Copilot commented on code in PR #2303:
URL: https://github.com/apache/age/pull/2303#discussion_r3335857621
##########
src/backend/utils/adt/agtype.c:
##########
@@ -2463,6 +2551,308 @@ Datum make_edge(Datum id, Datum startid, Datum endid,
Datum label,
properties);
}
+PG_FUNCTION_INFO_V1(vertex_to_agtype);
+
+/*
+ * Cast function: vertex -> agtype
+ * Vertex: (id graphid, label agtype, properties agtype)
+ */
+Datum vertex_to_agtype(PG_FUNCTION_ARGS)
+{
+ HeapTupleHeader rec;
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+ Datum *values;
+ bool *nulls;
+ graphid id;
+ agtype *label;
+ agtype *properties;
+ Datum result;
+
+ rec = PG_GETARG_HEAPTUPLEHEADER(0);
+
+ tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec),
+ HeapTupleHeaderGetTypMod(rec));
+
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(3 * sizeof(Datum));
+ nulls = (bool *) palloc(3 * sizeof(bool));
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ if (nulls[0])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex id cannot be NULL")));
+ if (nulls[1])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex label cannot be NULL")));
+
+ id = DatumGetInt64(values[0]);
+ label = DATUM_GET_AGTYPE_P(values[1]);
+ properties = nulls[2] ? NULL : DATUM_GET_AGTYPE_P(values[2]);
+
+ if (properties == NULL)
+ {
+ agtype_build_state *bstate = init_agtype_build_state(0, AGT_FOBJECT);
+ properties = build_agtype(bstate);
+ pfree_agtype_build_state(bstate);
+ }
+
+ result = make_vertex(Int64GetDatum(id), PointerGetDatum(label),
+ PointerGetDatum(properties));
+
+ ReleaseTupleDesc(tupdesc);
+ pfree(values);
+ pfree(nulls);
+
+ return result;
+}
+
+PG_FUNCTION_INFO_V1(edge_to_agtype);
+
+/*
+ * Cast function: edge -> agtype
+ * Edge: (id graphid, label agtype, end_id graphid, start_id graphid,
properties agtype)
+ */
+Datum edge_to_agtype(PG_FUNCTION_ARGS)
+{
+ HeapTupleHeader rec;
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+ Datum *values;
+ bool *nulls;
+ graphid id, start_id, end_id;
+ agtype *label;
+ agtype *properties;
+ Datum result;
+
+ rec = PG_GETARG_HEAPTUPLEHEADER(0);
+
+ tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec),
+ HeapTupleHeaderGetTypMod(rec));
+
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(5 * sizeof(Datum));
+ nulls = (bool *) palloc(5 * sizeof(bool));
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ if (nulls[0])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge id cannot be NULL")));
+ if (nulls[1])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge label cannot be NULL")));
+ if (nulls[2])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge end_id cannot be NULL")));
+ if (nulls[3])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge start_id cannot be NULL")));
+
+ id = DatumGetInt64(values[0]);
+ label = DATUM_GET_AGTYPE_P(values[1]);
+ end_id = DatumGetInt64(values[2]);
+ start_id = DatumGetInt64(values[3]);
+ properties = nulls[4] ? NULL : DATUM_GET_AGTYPE_P(values[4]);
+
+ if (properties == NULL)
+ {
+ agtype_build_state *bstate = init_agtype_build_state(0, AGT_FOBJECT);
+ properties = build_agtype(bstate);
+ pfree_agtype_build_state(bstate);
+ }
+
+ result = make_edge(Int64GetDatum(id), Int64GetDatum(start_id),
+ Int64GetDatum(end_id), PointerGetDatum(label),
+ PointerGetDatum(properties));
+
+ ReleaseTupleDesc(tupdesc);
+ pfree(values);
+ pfree(nulls);
+
+ return result;
+}
+
+/*
+ * Helper function to build JSON string from vertex composite type
+ */
+static char *vertex_to_json_string(HeapTupleHeader rec)
+{
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+ Datum *values;
+ bool *nulls;
+ graphid id;
+ agtype *label;
+ agtype *properties;
+ StringInfoData buf;
+ char *label_str;
+ char *props_str;
+
+ tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec),
+ HeapTupleHeaderGetTypMod(rec));
+
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(3 * sizeof(Datum));
+ nulls = (bool *) palloc(3 * sizeof(bool));
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ if (nulls[0])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex id cannot be NULL")));
+ if (nulls[1])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex label cannot be NULL")));
+
+ id = DatumGetInt64(values[0]);
+ label = DATUM_GET_AGTYPE_P(values[1]);
+ properties = nulls[2] ? NULL : DATUM_GET_AGTYPE_P(values[2]);
+
+ label_str = agtype_to_cstring(NULL, &label->root, VARSIZE(label));
+
+ if (properties != NULL)
+ {
+ props_str = agtype_to_cstring_worker(NULL, &properties->root,
+ VARSIZE(properties), false,
false);
+ }
+ else
+ {
+ props_str = "{}";
+ }
+
+ initStringInfo(&buf);
+ appendStringInfo(&buf, "{\"id\": %ld, \"label\": %s, \"properties\": %s}",
+ id, label_str, props_str);
+
+ ReleaseTupleDesc(tupdesc);
+ pfree(values);
+ pfree(nulls);
+
+ return buf.data;
+}
+
+/*
+ * Helper function to build JSON string from edge composite type
+ * Edge: (id graphid, label agtype, end_id graphid, start_id graphid,
properties agtype)
+ */
+static char *edge_to_json_string(HeapTupleHeader rec)
+{
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+ Datum *values;
+ bool *nulls;
+ graphid id, start_id, end_id;
+ agtype *label;
+ agtype *properties;
+ StringInfoData buf;
+ char *label_str;
+ char *props_str;
+
+ tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec),
+ HeapTupleHeaderGetTypMod(rec));
+
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(5 * sizeof(Datum));
+ nulls = (bool *) palloc(5 * sizeof(bool));
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ if (nulls[0])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge id cannot be NULL")));
+ if (nulls[1])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge label cannot be NULL")));
+ if (nulls[2])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge end_id cannot be NULL")));
+ if (nulls[3])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge start_id cannot be NULL")));
+
+ id = DatumGetInt64(values[0]);
+ label = DATUM_GET_AGTYPE_P(values[1]);
+ end_id = DatumGetInt64(values[2]);
+ start_id = DatumGetInt64(values[3]);
+ properties = nulls[4] ? NULL : DATUM_GET_AGTYPE_P(values[4]);
+
+ label_str = agtype_to_cstring(NULL, &label->root, VARSIZE(label));
+
+ if (properties != NULL)
+ {
+ props_str = agtype_to_cstring_worker(NULL, &properties->root,
+ VARSIZE(properties), false,
false);
+ }
+ else
+ {
+ props_str = "{}";
+ }
+
+ initStringInfo(&buf);
+ appendStringInfo(&buf, "{\"id\": %ld, \"label\": %s, \"end_id\": %ld,
\"start_id\": %ld, \"properties\": %s}",
+ id, label_str, end_id, start_id, props_str);
Review Comment:
Same portability issue as vertex JSON: `%ld` is not guaranteed to match
`int64`/`graphid` and can produce incorrect JSON on some platforms. Use
`INT64_FORMAT` for `id`, `start_id`, and `end_id`.
##########
src/include/utils/agtype.h:
##########
@@ -669,4 +669,11 @@ void clear_global_Oids_AGTYPE(void);
#define AGTYPEOID get_AGTYPEOID()
#define AGTYPEARRAYOID get_AGTYPEARRAYOID()
+/* Oid accessors for vertex and edge composite types */
+Oid get_VERTEXOID(void);
+Oid get_EDGEOID(void);
+void clear_global_Oids_VERTEX_EDGE(void);
+#define VERTEXOID get_VERTEXOID()
+#define EDGEOID get_EDGEOID()
Review Comment:
New cached OIDs for `vertex`/`edge` are introduced along with
`clear_global_Oids_VERTEX_EDGE()`, but the extension drop path currently only
clears AGTYPE and GRAPHID OIDs (see src/backend/catalog/ag_catalog.c). If AGE
is dropped and recreated in the same backend, the cached VERTEX/EDGE OIDs can
become stale. Please ensure the drop-extension cleanup calls
`clear_global_Oids_VERTEX_EDGE()` as well.
##########
src/backend/utils/adt/agtype.c:
##########
@@ -2463,6 +2551,308 @@ Datum make_edge(Datum id, Datum startid, Datum endid,
Datum label,
properties);
}
+PG_FUNCTION_INFO_V1(vertex_to_agtype);
+
+/*
+ * Cast function: vertex -> agtype
+ * Vertex: (id graphid, label agtype, properties agtype)
+ */
+Datum vertex_to_agtype(PG_FUNCTION_ARGS)
+{
+ HeapTupleHeader rec;
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+ Datum *values;
+ bool *nulls;
+ graphid id;
+ agtype *label;
+ agtype *properties;
+ Datum result;
+
+ rec = PG_GETARG_HEAPTUPLEHEADER(0);
+
+ tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec),
+ HeapTupleHeaderGetTypMod(rec));
+
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(3 * sizeof(Datum));
+ nulls = (bool *) palloc(3 * sizeof(bool));
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ if (nulls[0])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex id cannot be NULL")));
+ if (nulls[1])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex label cannot be NULL")));
+
+ id = DatumGetInt64(values[0]);
+ label = DATUM_GET_AGTYPE_P(values[1]);
+ properties = nulls[2] ? NULL : DATUM_GET_AGTYPE_P(values[2]);
+
+ if (properties == NULL)
+ {
+ agtype_build_state *bstate = init_agtype_build_state(0, AGT_FOBJECT);
+ properties = build_agtype(bstate);
+ pfree_agtype_build_state(bstate);
+ }
+
+ result = make_vertex(Int64GetDatum(id), PointerGetDatum(label),
+ PointerGetDatum(properties));
+
+ ReleaseTupleDesc(tupdesc);
+ pfree(values);
+ pfree(nulls);
+
+ return result;
+}
+
+PG_FUNCTION_INFO_V1(edge_to_agtype);
+
+/*
+ * Cast function: edge -> agtype
+ * Edge: (id graphid, label agtype, end_id graphid, start_id graphid,
properties agtype)
+ */
+Datum edge_to_agtype(PG_FUNCTION_ARGS)
+{
+ HeapTupleHeader rec;
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+ Datum *values;
+ bool *nulls;
+ graphid id, start_id, end_id;
+ agtype *label;
+ agtype *properties;
+ Datum result;
+
+ rec = PG_GETARG_HEAPTUPLEHEADER(0);
+
+ tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec),
+ HeapTupleHeaderGetTypMod(rec));
+
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(5 * sizeof(Datum));
+ nulls = (bool *) palloc(5 * sizeof(bool));
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ if (nulls[0])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge id cannot be NULL")));
+ if (nulls[1])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge label cannot be NULL")));
+ if (nulls[2])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge end_id cannot be NULL")));
+ if (nulls[3])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge start_id cannot be NULL")));
+
+ id = DatumGetInt64(values[0]);
+ label = DATUM_GET_AGTYPE_P(values[1]);
+ end_id = DatumGetInt64(values[2]);
+ start_id = DatumGetInt64(values[3]);
+ properties = nulls[4] ? NULL : DATUM_GET_AGTYPE_P(values[4]);
+
+ if (properties == NULL)
+ {
+ agtype_build_state *bstate = init_agtype_build_state(0, AGT_FOBJECT);
+ properties = build_agtype(bstate);
+ pfree_agtype_build_state(bstate);
+ }
+
+ result = make_edge(Int64GetDatum(id), Int64GetDatum(start_id),
+ Int64GetDatum(end_id), PointerGetDatum(label),
+ PointerGetDatum(properties));
+
+ ReleaseTupleDesc(tupdesc);
+ pfree(values);
+ pfree(nulls);
+
+ return result;
+}
+
+/*
+ * Helper function to build JSON string from vertex composite type
+ */
+static char *vertex_to_json_string(HeapTupleHeader rec)
+{
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+ Datum *values;
+ bool *nulls;
+ graphid id;
+ agtype *label;
+ agtype *properties;
+ StringInfoData buf;
+ char *label_str;
+ char *props_str;
+
+ tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec),
+ HeapTupleHeaderGetTypMod(rec));
+
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(3 * sizeof(Datum));
+ nulls = (bool *) palloc(3 * sizeof(bool));
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ if (nulls[0])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex id cannot be NULL")));
+ if (nulls[1])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex label cannot be NULL")));
+
+ id = DatumGetInt64(values[0]);
+ label = DATUM_GET_AGTYPE_P(values[1]);
+ properties = nulls[2] ? NULL : DATUM_GET_AGTYPE_P(values[2]);
+
+ label_str = agtype_to_cstring(NULL, &label->root, VARSIZE(label));
+
+ if (properties != NULL)
+ {
+ props_str = agtype_to_cstring_worker(NULL, &properties->root,
+ VARSIZE(properties), false,
false);
+ }
+ else
+ {
+ props_str = "{}";
+ }
+
+ initStringInfo(&buf);
+ appendStringInfo(&buf, "{\"id\": %ld, \"label\": %s, \"properties\": %s}",
+ id, label_str, props_str);
Review Comment:
The JSON builder uses `%ld` for `graphid` fields, but `graphid` is `int64`
(see utils/graphid.h). `%ld` is not portable across platforms (e.g., Windows)
and can mis-format 64-bit values. Use `INT64_FORMAT` for all `graphid`
substitutions.
##########
sql/agtype_graphid.sql:
##########
@@ -69,14 +96,163 @@ AS 'MODULE_PATHNAME';
-- agtype - edge
--
CREATE FUNCTION ag_catalog._agtype_build_edge(graphid, graphid, graphid,
- cstring, agtype)
+ agtype, agtype)
RETURNS agtype
LANGUAGE c
IMMUTABLE
CALLED ON NULL INPUT
PARALLEL SAFE
AS 'MODULE_PATHNAME';
+--
+-- vertex/edge to agtype cast functions
+--
+CREATE FUNCTION ag_catalog.vertex_to_agtype(vertex)
+ RETURNS agtype
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION ag_catalog.edge_to_agtype(edge)
+ RETURNS agtype
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+--
+-- Implicit casts from vertex/edge to agtype
+--
+CREATE CAST (vertex AS agtype)
+ WITH FUNCTION ag_catalog.vertex_to_agtype(vertex)
+AS IMPLICIT;
+
+CREATE CAST (edge AS agtype)
+ WITH FUNCTION ag_catalog.edge_to_agtype(edge)
+AS IMPLICIT;
+
+CREATE FUNCTION ag_catalog.vertex_to_json(vertex)
+ RETURNS json
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION ag_catalog.edge_to_json(edge)
+ RETURNS json
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE CAST (vertex AS json)
+ WITH FUNCTION ag_catalog.vertex_to_json(vertex);
+
+CREATE CAST (edge AS json)
+ WITH FUNCTION ag_catalog.edge_to_json(edge);
+
+CREATE FUNCTION ag_catalog.vertex_to_jsonb(vertex)
+ RETURNS jsonb
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION ag_catalog.edge_to_jsonb(edge)
+ RETURNS jsonb
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE CAST (vertex AS jsonb)
+ WITH FUNCTION ag_catalog.vertex_to_jsonb(vertex);
+
+CREATE CAST (edge AS jsonb)
+ WITH FUNCTION ag_catalog.edge_to_jsonb(edge);
+
+--
+-- Equality operators for vertex and edge (compare by id)
+--
+CREATE FUNCTION ag_catalog.vertex_eq(vertex, vertex)
+ RETURNS boolean
+ LANGUAGE sql
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS $$ SELECT $1.id = $2.id $$;
+
+CREATE OPERATOR = (
+ FUNCTION = ag_catalog.vertex_eq,
+ LEFTARG = vertex,
+ RIGHTARG = vertex,
+ COMMUTATOR = =,
+ NEGATOR = <>,
+ RESTRICT = eqsel,
+ JOIN = eqjoinsel
+);
+
+CREATE FUNCTION ag_catalog.vertex_ne(vertex, vertex)
+ RETURNS boolean
+ LANGUAGE sql
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS $$ SELECT $1.id <> $2.id $$;
+
+CREATE OPERATOR <> (
+ FUNCTION = ag_catalog.vertex_ne,
+ LEFTARG = vertex,
+ RIGHTARG = vertex,
+ COMMUTATOR = <>,
+ NEGATOR = =,
+ RESTRICT = neqsel,
+ JOIN = neqjoinsel
+);
+
+CREATE FUNCTION ag_catalog.edge_eq(edge, edge)
+ RETURNS boolean
+ LANGUAGE sql
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS $$ SELECT $1.id = $2.id $$;
+
+CREATE OPERATOR = (
+ FUNCTION = ag_catalog.edge_eq,
+ LEFTARG = edge,
+ RIGHTARG = edge,
+ COMMUTATOR = =,
+ NEGATOR = <>,
+ RESTRICT = eqsel,
+ JOIN = eqjoinsel
+);
Review Comment:
Same planning concern as `vertex = vertex`: the edge equality operator isn't
marked `HASHES`/`MERGES`, which can prevent hash/merge join selection when the
operator is referenced directly. Adding these flags matches the pattern used by
other equality operators in this extension (e.g., `graphid = graphid`).
##########
sql/age_scalar.sql:
##########
@@ -93,6 +93,15 @@ RETURNS NULL ON NULL INPUT
PARALLEL SAFE
AS 'MODULE_PATHNAME';
+-- Helper function for optimized startNode/endNode
+CREATE FUNCTION ag_catalog._get_vertex_by_graphid(text, graphid)
+ RETURNS agtype
+ LANGUAGE c
+ STABLE
+RETURNS NULL ON NULL INPUT
Review Comment:
The upgrade script revokes PUBLIC access to
`ag_catalog._get_vertex_by_graphid(text, graphid)`, but the base install SQL
here does not. That makes privileges differ depending on whether the function
is created via fresh install vs upgrade. Consider adding the same REVOKE here
(or dropping it from the upgrade script) to keep behavior consistent.
##########
src/backend/utils/adt/agtype.c:
##########
@@ -2463,6 +2551,308 @@ Datum make_edge(Datum id, Datum startid, Datum endid,
Datum label,
properties);
}
+PG_FUNCTION_INFO_V1(vertex_to_agtype);
+
+/*
+ * Cast function: vertex -> agtype
+ * Vertex: (id graphid, label agtype, properties agtype)
+ */
+Datum vertex_to_agtype(PG_FUNCTION_ARGS)
+{
+ HeapTupleHeader rec;
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+ Datum *values;
+ bool *nulls;
+ graphid id;
+ agtype *label;
+ agtype *properties;
+ Datum result;
+
+ rec = PG_GETARG_HEAPTUPLEHEADER(0);
+
+ tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec),
+ HeapTupleHeaderGetTypMod(rec));
+
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(3 * sizeof(Datum));
+ nulls = (bool *) palloc(3 * sizeof(bool));
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ if (nulls[0])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex id cannot be NULL")));
+ if (nulls[1])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex label cannot be NULL")));
+
+ id = DatumGetInt64(values[0]);
+ label = DATUM_GET_AGTYPE_P(values[1]);
+ properties = nulls[2] ? NULL : DATUM_GET_AGTYPE_P(values[2]);
+
+ if (properties == NULL)
+ {
+ agtype_build_state *bstate = init_agtype_build_state(0, AGT_FOBJECT);
+ properties = build_agtype(bstate);
+ pfree_agtype_build_state(bstate);
+ }
+
+ result = make_vertex(Int64GetDatum(id), PointerGetDatum(label),
+ PointerGetDatum(properties));
+
+ ReleaseTupleDesc(tupdesc);
+ pfree(values);
+ pfree(nulls);
+
+ return result;
+}
+
+PG_FUNCTION_INFO_V1(edge_to_agtype);
+
+/*
+ * Cast function: edge -> agtype
+ * Edge: (id graphid, label agtype, end_id graphid, start_id graphid,
properties agtype)
+ */
+Datum edge_to_agtype(PG_FUNCTION_ARGS)
+{
+ HeapTupleHeader rec;
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+ Datum *values;
+ bool *nulls;
+ graphid id, start_id, end_id;
+ agtype *label;
+ agtype *properties;
+ Datum result;
+
+ rec = PG_GETARG_HEAPTUPLEHEADER(0);
+
+ tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec),
+ HeapTupleHeaderGetTypMod(rec));
+
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(5 * sizeof(Datum));
+ nulls = (bool *) palloc(5 * sizeof(bool));
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ if (nulls[0])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge id cannot be NULL")));
+ if (nulls[1])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge label cannot be NULL")));
+ if (nulls[2])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge end_id cannot be NULL")));
+ if (nulls[3])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge start_id cannot be NULL")));
+
+ id = DatumGetInt64(values[0]);
+ label = DATUM_GET_AGTYPE_P(values[1]);
+ end_id = DatumGetInt64(values[2]);
+ start_id = DatumGetInt64(values[3]);
+ properties = nulls[4] ? NULL : DATUM_GET_AGTYPE_P(values[4]);
+
+ if (properties == NULL)
+ {
+ agtype_build_state *bstate = init_agtype_build_state(0, AGT_FOBJECT);
+ properties = build_agtype(bstate);
+ pfree_agtype_build_state(bstate);
+ }
+
+ result = make_edge(Int64GetDatum(id), Int64GetDatum(start_id),
+ Int64GetDatum(end_id), PointerGetDatum(label),
+ PointerGetDatum(properties));
+
+ ReleaseTupleDesc(tupdesc);
+ pfree(values);
+ pfree(nulls);
+
+ return result;
+}
+
+/*
+ * Helper function to build JSON string from vertex composite type
+ */
+static char *vertex_to_json_string(HeapTupleHeader rec)
+{
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+ Datum *values;
+ bool *nulls;
+ graphid id;
+ agtype *label;
+ agtype *properties;
+ StringInfoData buf;
+ char *label_str;
+ char *props_str;
+
+ tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec),
+ HeapTupleHeaderGetTypMod(rec));
+
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(3 * sizeof(Datum));
+ nulls = (bool *) palloc(3 * sizeof(bool));
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ if (nulls[0])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex id cannot be NULL")));
+ if (nulls[1])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex label cannot be NULL")));
+
+ id = DatumGetInt64(values[0]);
+ label = DATUM_GET_AGTYPE_P(values[1]);
+ properties = nulls[2] ? NULL : DATUM_GET_AGTYPE_P(values[2]);
+
+ label_str = agtype_to_cstring(NULL, &label->root, VARSIZE(label));
+
+ if (properties != NULL)
+ {
+ props_str = agtype_to_cstring_worker(NULL, &properties->root,
+ VARSIZE(properties), false,
false);
+ }
+ else
+ {
+ props_str = "{}";
+ }
+
+ initStringInfo(&buf);
+ appendStringInfo(&buf, "{\"id\": %ld, \"label\": %s, \"properties\": %s}",
+ id, label_str, props_str);
Review Comment:
The JSON builder uses `%ld` for `graphid` fields, but `graphid` is `int64`
(see utils/graphid.h). `%ld` is not portable across platforms (e.g., Windows)
and can mis-format 64-bit values. Use `INT64_FORMAT` for all `graphid`
substitutions.
##########
sql/agtype_graphid.sql:
##########
@@ -69,14 +96,163 @@ AS 'MODULE_PATHNAME';
-- agtype - edge
--
CREATE FUNCTION ag_catalog._agtype_build_edge(graphid, graphid, graphid,
- cstring, agtype)
+ agtype, agtype)
RETURNS agtype
LANGUAGE c
IMMUTABLE
CALLED ON NULL INPUT
PARALLEL SAFE
AS 'MODULE_PATHNAME';
+--
+-- vertex/edge to agtype cast functions
+--
+CREATE FUNCTION ag_catalog.vertex_to_agtype(vertex)
+ RETURNS agtype
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION ag_catalog.edge_to_agtype(edge)
+ RETURNS agtype
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+--
+-- Implicit casts from vertex/edge to agtype
+--
+CREATE CAST (vertex AS agtype)
+ WITH FUNCTION ag_catalog.vertex_to_agtype(vertex)
+AS IMPLICIT;
+
+CREATE CAST (edge AS agtype)
+ WITH FUNCTION ag_catalog.edge_to_agtype(edge)
+AS IMPLICIT;
+
+CREATE FUNCTION ag_catalog.vertex_to_json(vertex)
+ RETURNS json
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION ag_catalog.edge_to_json(edge)
+ RETURNS json
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE CAST (vertex AS json)
+ WITH FUNCTION ag_catalog.vertex_to_json(vertex);
+
+CREATE CAST (edge AS json)
+ WITH FUNCTION ag_catalog.edge_to_json(edge);
+
+CREATE FUNCTION ag_catalog.vertex_to_jsonb(vertex)
+ RETURNS jsonb
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION ag_catalog.edge_to_jsonb(edge)
+ RETURNS jsonb
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE CAST (vertex AS jsonb)
+ WITH FUNCTION ag_catalog.vertex_to_jsonb(vertex);
+
+CREATE CAST (edge AS jsonb)
+ WITH FUNCTION ag_catalog.edge_to_jsonb(edge);
+
+--
+-- Equality operators for vertex and edge (compare by id)
+--
+CREATE FUNCTION ag_catalog.vertex_eq(vertex, vertex)
+ RETURNS boolean
+ LANGUAGE sql
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS $$ SELECT $1.id = $2.id $$;
+
+CREATE OPERATOR = (
+ FUNCTION = ag_catalog.vertex_eq,
+ LEFTARG = vertex,
+ RIGHTARG = vertex,
+ COMMUTATOR = =,
+ NEGATOR = <>,
+ RESTRICT = eqsel,
+ JOIN = eqjoinsel
+);
Review Comment:
The vertex equality operator is intended to behave like an equality operator
for planning purposes, but it isn't marked `HASHES`/`MERGES` (unlike the
existing `graphid` `=` operator). Without these flags, the planner may miss
hash/merge join optimizations when the operator itself (not its inlined SQL
body) is used. Consider adding `HASHES, MERGES` here.
##########
src/backend/utils/adt/agtype.c:
##########
@@ -2463,6 +2551,308 @@ Datum make_edge(Datum id, Datum startid, Datum endid,
Datum label,
properties);
}
+PG_FUNCTION_INFO_V1(vertex_to_agtype);
+
+/*
+ * Cast function: vertex -> agtype
+ * Vertex: (id graphid, label agtype, properties agtype)
+ */
+Datum vertex_to_agtype(PG_FUNCTION_ARGS)
+{
+ HeapTupleHeader rec;
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+ Datum *values;
+ bool *nulls;
+ graphid id;
+ agtype *label;
+ agtype *properties;
+ Datum result;
+
+ rec = PG_GETARG_HEAPTUPLEHEADER(0);
+
+ tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec),
+ HeapTupleHeaderGetTypMod(rec));
+
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(3 * sizeof(Datum));
+ nulls = (bool *) palloc(3 * sizeof(bool));
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ if (nulls[0])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex id cannot be NULL")));
+ if (nulls[1])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex label cannot be NULL")));
+
+ id = DatumGetInt64(values[0]);
+ label = DATUM_GET_AGTYPE_P(values[1]);
+ properties = nulls[2] ? NULL : DATUM_GET_AGTYPE_P(values[2]);
+
+ if (properties == NULL)
+ {
+ agtype_build_state *bstate = init_agtype_build_state(0, AGT_FOBJECT);
+ properties = build_agtype(bstate);
+ pfree_agtype_build_state(bstate);
+ }
+
+ result = make_vertex(Int64GetDatum(id), PointerGetDatum(label),
+ PointerGetDatum(properties));
+
+ ReleaseTupleDesc(tupdesc);
+ pfree(values);
+ pfree(nulls);
+
+ return result;
+}
+
+PG_FUNCTION_INFO_V1(edge_to_agtype);
+
+/*
+ * Cast function: edge -> agtype
+ * Edge: (id graphid, label agtype, end_id graphid, start_id graphid,
properties agtype)
+ */
+Datum edge_to_agtype(PG_FUNCTION_ARGS)
+{
+ HeapTupleHeader rec;
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+ Datum *values;
+ bool *nulls;
+ graphid id, start_id, end_id;
+ agtype *label;
+ agtype *properties;
+ Datum result;
+
+ rec = PG_GETARG_HEAPTUPLEHEADER(0);
+
+ tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec),
+ HeapTupleHeaderGetTypMod(rec));
+
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(5 * sizeof(Datum));
+ nulls = (bool *) palloc(5 * sizeof(bool));
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ if (nulls[0])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge id cannot be NULL")));
+ if (nulls[1])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge label cannot be NULL")));
+ if (nulls[2])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge end_id cannot be NULL")));
+ if (nulls[3])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge start_id cannot be NULL")));
+
+ id = DatumGetInt64(values[0]);
+ label = DATUM_GET_AGTYPE_P(values[1]);
+ end_id = DatumGetInt64(values[2]);
+ start_id = DatumGetInt64(values[3]);
+ properties = nulls[4] ? NULL : DATUM_GET_AGTYPE_P(values[4]);
+
+ if (properties == NULL)
+ {
+ agtype_build_state *bstate = init_agtype_build_state(0, AGT_FOBJECT);
+ properties = build_agtype(bstate);
+ pfree_agtype_build_state(bstate);
+ }
+
+ result = make_edge(Int64GetDatum(id), Int64GetDatum(start_id),
+ Int64GetDatum(end_id), PointerGetDatum(label),
+ PointerGetDatum(properties));
+
+ ReleaseTupleDesc(tupdesc);
+ pfree(values);
+ pfree(nulls);
+
+ return result;
+}
+
+/*
+ * Helper function to build JSON string from vertex composite type
+ */
+static char *vertex_to_json_string(HeapTupleHeader rec)
+{
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+ Datum *values;
+ bool *nulls;
+ graphid id;
+ agtype *label;
+ agtype *properties;
+ StringInfoData buf;
+ char *label_str;
+ char *props_str;
+
+ tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec),
+ HeapTupleHeaderGetTypMod(rec));
+
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(3 * sizeof(Datum));
+ nulls = (bool *) palloc(3 * sizeof(bool));
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ if (nulls[0])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex id cannot be NULL")));
+ if (nulls[1])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex label cannot be NULL")));
+
+ id = DatumGetInt64(values[0]);
+ label = DATUM_GET_AGTYPE_P(values[1]);
+ properties = nulls[2] ? NULL : DATUM_GET_AGTYPE_P(values[2]);
+
+ label_str = agtype_to_cstring(NULL, &label->root, VARSIZE(label));
+
+ if (properties != NULL)
+ {
+ props_str = agtype_to_cstring_worker(NULL, &properties->root,
+ VARSIZE(properties), false,
false);
+ }
+ else
+ {
+ props_str = "{}";
+ }
+
+ initStringInfo(&buf);
+ appendStringInfo(&buf, "{\"id\": %ld, \"label\": %s, \"properties\": %s}",
+ id, label_str, props_str);
+
+ ReleaseTupleDesc(tupdesc);
+ pfree(values);
+ pfree(nulls);
+
+ return buf.data;
+}
+
+/*
+ * Helper function to build JSON string from edge composite type
+ * Edge: (id graphid, label agtype, end_id graphid, start_id graphid,
properties agtype)
+ */
+static char *edge_to_json_string(HeapTupleHeader rec)
+{
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+ Datum *values;
+ bool *nulls;
+ graphid id, start_id, end_id;
+ agtype *label;
+ agtype *properties;
+ StringInfoData buf;
+ char *label_str;
+ char *props_str;
+
+ tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec),
+ HeapTupleHeaderGetTypMod(rec));
+
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(5 * sizeof(Datum));
+ nulls = (bool *) palloc(5 * sizeof(bool));
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ if (nulls[0])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge id cannot be NULL")));
+ if (nulls[1])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge label cannot be NULL")));
+ if (nulls[2])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge end_id cannot be NULL")));
+ if (nulls[3])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge start_id cannot be NULL")));
+
+ id = DatumGetInt64(values[0]);
+ label = DATUM_GET_AGTYPE_P(values[1]);
+ end_id = DatumGetInt64(values[2]);
+ start_id = DatumGetInt64(values[3]);
+ properties = nulls[4] ? NULL : DATUM_GET_AGTYPE_P(values[4]);
+
+ label_str = agtype_to_cstring(NULL, &label->root, VARSIZE(label));
+
+ if (properties != NULL)
+ {
+ props_str = agtype_to_cstring_worker(NULL, &properties->root,
+ VARSIZE(properties), false,
false);
+ }
+ else
+ {
+ props_str = "{}";
+ }
+
+ initStringInfo(&buf);
+ appendStringInfo(&buf, "{\"id\": %ld, \"label\": %s, \"end_id\": %ld,
\"start_id\": %ld, \"properties\": %s}",
+ id, label_str, end_id, start_id, props_str);
Review Comment:
Same portability issue as vertex JSON: `%ld` is not guaranteed to match
`int64`/`graphid` and can produce incorrect JSON on some platforms. Use
`INT64_FORMAT` for `id`, `start_id`, and `end_id`.
##########
src/include/utils/agtype.h:
##########
@@ -669,4 +669,11 @@ void clear_global_Oids_AGTYPE(void);
#define AGTYPEOID get_AGTYPEOID()
#define AGTYPEARRAYOID get_AGTYPEARRAYOID()
+/* Oid accessors for vertex and edge composite types */
+Oid get_VERTEXOID(void);
+Oid get_EDGEOID(void);
+void clear_global_Oids_VERTEX_EDGE(void);
+#define VERTEXOID get_VERTEXOID()
+#define EDGEOID get_EDGEOID()
Review Comment:
New cached OIDs for `vertex`/`edge` are introduced along with
`clear_global_Oids_VERTEX_EDGE()`, but the extension drop path currently only
clears AGTYPE and GRAPHID OIDs (see src/backend/catalog/ag_catalog.c). If AGE
is dropped and recreated in the same backend, the cached VERTEX/EDGE OIDs can
become stale. Please ensure the drop-extension cleanup calls
`clear_global_Oids_VERTEX_EDGE()` as well.
##########
sql/agtype_graphid.sql:
##########
@@ -69,14 +96,163 @@ AS 'MODULE_PATHNAME';
-- agtype - edge
--
CREATE FUNCTION ag_catalog._agtype_build_edge(graphid, graphid, graphid,
- cstring, agtype)
+ agtype, agtype)
RETURNS agtype
LANGUAGE c
IMMUTABLE
CALLED ON NULL INPUT
PARALLEL SAFE
AS 'MODULE_PATHNAME';
+--
+-- vertex/edge to agtype cast functions
+--
+CREATE FUNCTION ag_catalog.vertex_to_agtype(vertex)
+ RETURNS agtype
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION ag_catalog.edge_to_agtype(edge)
+ RETURNS agtype
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+--
+-- Implicit casts from vertex/edge to agtype
+--
+CREATE CAST (vertex AS agtype)
+ WITH FUNCTION ag_catalog.vertex_to_agtype(vertex)
+AS IMPLICIT;
+
+CREATE CAST (edge AS agtype)
+ WITH FUNCTION ag_catalog.edge_to_agtype(edge)
+AS IMPLICIT;
+
+CREATE FUNCTION ag_catalog.vertex_to_json(vertex)
+ RETURNS json
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION ag_catalog.edge_to_json(edge)
+ RETURNS json
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE CAST (vertex AS json)
+ WITH FUNCTION ag_catalog.vertex_to_json(vertex);
+
+CREATE CAST (edge AS json)
+ WITH FUNCTION ag_catalog.edge_to_json(edge);
+
+CREATE FUNCTION ag_catalog.vertex_to_jsonb(vertex)
+ RETURNS jsonb
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION ag_catalog.edge_to_jsonb(edge)
+ RETURNS jsonb
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE CAST (vertex AS jsonb)
+ WITH FUNCTION ag_catalog.vertex_to_jsonb(vertex);
+
+CREATE CAST (edge AS jsonb)
+ WITH FUNCTION ag_catalog.edge_to_jsonb(edge);
+
+--
+-- Equality operators for vertex and edge (compare by id)
+--
+CREATE FUNCTION ag_catalog.vertex_eq(vertex, vertex)
+ RETURNS boolean
+ LANGUAGE sql
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS $$ SELECT $1.id = $2.id $$;
+
+CREATE OPERATOR = (
+ FUNCTION = ag_catalog.vertex_eq,
+ LEFTARG = vertex,
+ RIGHTARG = vertex,
+ COMMUTATOR = =,
+ NEGATOR = <>,
+ RESTRICT = eqsel,
+ JOIN = eqjoinsel
+);
Review Comment:
The vertex equality operator is intended to behave like an equality operator
for planning purposes, but it isn't marked `HASHES`/`MERGES` (unlike the
existing `graphid` `=` operator). Without these flags, the planner may miss
hash/merge join optimizations when the operator itself (not its inlined SQL
body) is used. Consider adding `HASHES, MERGES` here.
##########
src/backend/utils/adt/agtype.c:
##########
@@ -2463,6 +2551,308 @@ Datum make_edge(Datum id, Datum startid, Datum endid,
Datum label,
properties);
}
+PG_FUNCTION_INFO_V1(vertex_to_agtype);
+
+/*
+ * Cast function: vertex -> agtype
+ * Vertex: (id graphid, label agtype, properties agtype)
+ */
+Datum vertex_to_agtype(PG_FUNCTION_ARGS)
+{
+ HeapTupleHeader rec;
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+ Datum *values;
+ bool *nulls;
+ graphid id;
+ agtype *label;
+ agtype *properties;
+ Datum result;
+
+ rec = PG_GETARG_HEAPTUPLEHEADER(0);
+
+ tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec),
+ HeapTupleHeaderGetTypMod(rec));
+
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(3 * sizeof(Datum));
+ nulls = (bool *) palloc(3 * sizeof(bool));
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ if (nulls[0])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex id cannot be NULL")));
+ if (nulls[1])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex label cannot be NULL")));
+
+ id = DatumGetInt64(values[0]);
+ label = DATUM_GET_AGTYPE_P(values[1]);
+ properties = nulls[2] ? NULL : DATUM_GET_AGTYPE_P(values[2]);
+
+ if (properties == NULL)
+ {
+ agtype_build_state *bstate = init_agtype_build_state(0, AGT_FOBJECT);
+ properties = build_agtype(bstate);
+ pfree_agtype_build_state(bstate);
+ }
+
+ result = make_vertex(Int64GetDatum(id), PointerGetDatum(label),
+ PointerGetDatum(properties));
+
+ ReleaseTupleDesc(tupdesc);
+ pfree(values);
+ pfree(nulls);
+
+ return result;
+}
+
+PG_FUNCTION_INFO_V1(edge_to_agtype);
+
+/*
+ * Cast function: edge -> agtype
+ * Edge: (id graphid, label agtype, end_id graphid, start_id graphid,
properties agtype)
+ */
+Datum edge_to_agtype(PG_FUNCTION_ARGS)
+{
+ HeapTupleHeader rec;
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+ Datum *values;
+ bool *nulls;
+ graphid id, start_id, end_id;
+ agtype *label;
+ agtype *properties;
+ Datum result;
+
+ rec = PG_GETARG_HEAPTUPLEHEADER(0);
+
+ tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec),
+ HeapTupleHeaderGetTypMod(rec));
+
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(5 * sizeof(Datum));
+ nulls = (bool *) palloc(5 * sizeof(bool));
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ if (nulls[0])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge id cannot be NULL")));
+ if (nulls[1])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge label cannot be NULL")));
+ if (nulls[2])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge end_id cannot be NULL")));
+ if (nulls[3])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge start_id cannot be NULL")));
+
+ id = DatumGetInt64(values[0]);
+ label = DATUM_GET_AGTYPE_P(values[1]);
+ end_id = DatumGetInt64(values[2]);
+ start_id = DatumGetInt64(values[3]);
+ properties = nulls[4] ? NULL : DATUM_GET_AGTYPE_P(values[4]);
+
+ if (properties == NULL)
+ {
+ agtype_build_state *bstate = init_agtype_build_state(0, AGT_FOBJECT);
+ properties = build_agtype(bstate);
+ pfree_agtype_build_state(bstate);
+ }
+
+ result = make_edge(Int64GetDatum(id), Int64GetDatum(start_id),
+ Int64GetDatum(end_id), PointerGetDatum(label),
+ PointerGetDatum(properties));
+
+ ReleaseTupleDesc(tupdesc);
+ pfree(values);
+ pfree(nulls);
+
+ return result;
+}
+
+/*
+ * Helper function to build JSON string from vertex composite type
+ */
+static char *vertex_to_json_string(HeapTupleHeader rec)
+{
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+ Datum *values;
+ bool *nulls;
+ graphid id;
+ agtype *label;
+ agtype *properties;
+ StringInfoData buf;
+ char *label_str;
+ char *props_str;
+
+ tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec),
+ HeapTupleHeaderGetTypMod(rec));
+
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(3 * sizeof(Datum));
+ nulls = (bool *) palloc(3 * sizeof(bool));
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ if (nulls[0])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex id cannot be NULL")));
+ if (nulls[1])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex label cannot be NULL")));
+
+ id = DatumGetInt64(values[0]);
+ label = DATUM_GET_AGTYPE_P(values[1]);
+ properties = nulls[2] ? NULL : DATUM_GET_AGTYPE_P(values[2]);
+
+ label_str = agtype_to_cstring(NULL, &label->root, VARSIZE(label));
+
+ if (properties != NULL)
+ {
+ props_str = agtype_to_cstring_worker(NULL, &properties->root,
+ VARSIZE(properties), false,
false);
+ }
+ else
+ {
+ props_str = "{}";
+ }
+
+ initStringInfo(&buf);
+ appendStringInfo(&buf, "{\"id\": %ld, \"label\": %s, \"properties\": %s}",
+ id, label_str, props_str);
Review Comment:
The JSON builder uses `%ld` for `graphid` fields, but `graphid` is `int64`
(see utils/graphid.h). `%ld` is not portable across platforms (e.g., Windows)
and can mis-format 64-bit values. Use `INT64_FORMAT` for all `graphid`
substitutions.
##########
src/backend/utils/adt/agtype.c:
##########
@@ -2463,6 +2551,308 @@ Datum make_edge(Datum id, Datum startid, Datum endid,
Datum label,
properties);
}
+PG_FUNCTION_INFO_V1(vertex_to_agtype);
+
+/*
+ * Cast function: vertex -> agtype
+ * Vertex: (id graphid, label agtype, properties agtype)
+ */
+Datum vertex_to_agtype(PG_FUNCTION_ARGS)
+{
+ HeapTupleHeader rec;
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+ Datum *values;
+ bool *nulls;
+ graphid id;
+ agtype *label;
+ agtype *properties;
+ Datum result;
+
+ rec = PG_GETARG_HEAPTUPLEHEADER(0);
+
+ tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec),
+ HeapTupleHeaderGetTypMod(rec));
+
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(3 * sizeof(Datum));
+ nulls = (bool *) palloc(3 * sizeof(bool));
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ if (nulls[0])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex id cannot be NULL")));
+ if (nulls[1])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex label cannot be NULL")));
+
+ id = DatumGetInt64(values[0]);
+ label = DATUM_GET_AGTYPE_P(values[1]);
+ properties = nulls[2] ? NULL : DATUM_GET_AGTYPE_P(values[2]);
+
+ if (properties == NULL)
+ {
+ agtype_build_state *bstate = init_agtype_build_state(0, AGT_FOBJECT);
+ properties = build_agtype(bstate);
+ pfree_agtype_build_state(bstate);
+ }
+
+ result = make_vertex(Int64GetDatum(id), PointerGetDatum(label),
+ PointerGetDatum(properties));
+
+ ReleaseTupleDesc(tupdesc);
+ pfree(values);
+ pfree(nulls);
+
+ return result;
+}
+
+PG_FUNCTION_INFO_V1(edge_to_agtype);
+
+/*
+ * Cast function: edge -> agtype
+ * Edge: (id graphid, label agtype, end_id graphid, start_id graphid,
properties agtype)
+ */
+Datum edge_to_agtype(PG_FUNCTION_ARGS)
+{
+ HeapTupleHeader rec;
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+ Datum *values;
+ bool *nulls;
+ graphid id, start_id, end_id;
+ agtype *label;
+ agtype *properties;
+ Datum result;
+
+ rec = PG_GETARG_HEAPTUPLEHEADER(0);
+
+ tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec),
+ HeapTupleHeaderGetTypMod(rec));
+
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(5 * sizeof(Datum));
+ nulls = (bool *) palloc(5 * sizeof(bool));
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ if (nulls[0])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge id cannot be NULL")));
+ if (nulls[1])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge label cannot be NULL")));
+ if (nulls[2])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge end_id cannot be NULL")));
+ if (nulls[3])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge start_id cannot be NULL")));
+
+ id = DatumGetInt64(values[0]);
+ label = DATUM_GET_AGTYPE_P(values[1]);
+ end_id = DatumGetInt64(values[2]);
+ start_id = DatumGetInt64(values[3]);
+ properties = nulls[4] ? NULL : DATUM_GET_AGTYPE_P(values[4]);
+
+ if (properties == NULL)
+ {
+ agtype_build_state *bstate = init_agtype_build_state(0, AGT_FOBJECT);
+ properties = build_agtype(bstate);
+ pfree_agtype_build_state(bstate);
+ }
+
+ result = make_edge(Int64GetDatum(id), Int64GetDatum(start_id),
+ Int64GetDatum(end_id), PointerGetDatum(label),
+ PointerGetDatum(properties));
+
+ ReleaseTupleDesc(tupdesc);
+ pfree(values);
+ pfree(nulls);
+
+ return result;
+}
+
+/*
+ * Helper function to build JSON string from vertex composite type
+ */
+static char *vertex_to_json_string(HeapTupleHeader rec)
+{
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+ Datum *values;
+ bool *nulls;
+ graphid id;
+ agtype *label;
+ agtype *properties;
+ StringInfoData buf;
+ char *label_str;
+ char *props_str;
+
+ tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec),
+ HeapTupleHeaderGetTypMod(rec));
+
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(3 * sizeof(Datum));
+ nulls = (bool *) palloc(3 * sizeof(bool));
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ if (nulls[0])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex id cannot be NULL")));
+ if (nulls[1])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("vertex label cannot be NULL")));
+
+ id = DatumGetInt64(values[0]);
+ label = DATUM_GET_AGTYPE_P(values[1]);
+ properties = nulls[2] ? NULL : DATUM_GET_AGTYPE_P(values[2]);
+
+ label_str = agtype_to_cstring(NULL, &label->root, VARSIZE(label));
+
+ if (properties != NULL)
+ {
+ props_str = agtype_to_cstring_worker(NULL, &properties->root,
+ VARSIZE(properties), false,
false);
+ }
+ else
+ {
+ props_str = "{}";
+ }
+
+ initStringInfo(&buf);
+ appendStringInfo(&buf, "{\"id\": %ld, \"label\": %s, \"properties\": %s}",
+ id, label_str, props_str);
+
+ ReleaseTupleDesc(tupdesc);
+ pfree(values);
+ pfree(nulls);
+
+ return buf.data;
+}
+
+/*
+ * Helper function to build JSON string from edge composite type
+ * Edge: (id graphid, label agtype, end_id graphid, start_id graphid,
properties agtype)
+ */
+static char *edge_to_json_string(HeapTupleHeader rec)
+{
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+ Datum *values;
+ bool *nulls;
+ graphid id, start_id, end_id;
+ agtype *label;
+ agtype *properties;
+ StringInfoData buf;
+ char *label_str;
+ char *props_str;
+
+ tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec),
+ HeapTupleHeaderGetTypMod(rec));
+
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ tuple.t_data = rec;
+
+ values = (Datum *) palloc(5 * sizeof(Datum));
+ nulls = (bool *) palloc(5 * sizeof(bool));
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+
+ if (nulls[0])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge id cannot be NULL")));
+ if (nulls[1])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge label cannot be NULL")));
+ if (nulls[2])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge end_id cannot be NULL")));
+ if (nulls[3])
+ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("edge start_id cannot be NULL")));
+
+ id = DatumGetInt64(values[0]);
+ label = DATUM_GET_AGTYPE_P(values[1]);
+ end_id = DatumGetInt64(values[2]);
+ start_id = DatumGetInt64(values[3]);
+ properties = nulls[4] ? NULL : DATUM_GET_AGTYPE_P(values[4]);
+
+ label_str = agtype_to_cstring(NULL, &label->root, VARSIZE(label));
+
+ if (properties != NULL)
+ {
+ props_str = agtype_to_cstring_worker(NULL, &properties->root,
+ VARSIZE(properties), false,
false);
+ }
+ else
+ {
+ props_str = "{}";
+ }
+
+ initStringInfo(&buf);
+ appendStringInfo(&buf, "{\"id\": %ld, \"label\": %s, \"end_id\": %ld,
\"start_id\": %ld, \"properties\": %s}",
+ id, label_str, end_id, start_id, props_str);
Review Comment:
Same portability issue as vertex JSON: `%ld` is not guaranteed to match
`int64`/`graphid` and can produce incorrect JSON on some platforms. Use
`INT64_FORMAT` for `id`, `start_id`, and `end_id`.
##########
src/include/utils/agtype.h:
##########
@@ -669,4 +669,11 @@ void clear_global_Oids_AGTYPE(void);
#define AGTYPEOID get_AGTYPEOID()
#define AGTYPEARRAYOID get_AGTYPEARRAYOID()
+/* Oid accessors for vertex and edge composite types */
+Oid get_VERTEXOID(void);
+Oid get_EDGEOID(void);
+void clear_global_Oids_VERTEX_EDGE(void);
+#define VERTEXOID get_VERTEXOID()
+#define EDGEOID get_EDGEOID()
Review Comment:
New cached OIDs for `vertex`/`edge` are introduced along with
`clear_global_Oids_VERTEX_EDGE()`, but the extension drop path currently only
clears AGTYPE and GRAPHID OIDs (see src/backend/catalog/ag_catalog.c). If AGE
is dropped and recreated in the same backend, the cached VERTEX/EDGE OIDs can
become stale. Please ensure the drop-extension cleanup calls
`clear_global_Oids_VERTEX_EDGE()` as well.
##########
sql/agtype_graphid.sql:
##########
@@ -69,14 +96,163 @@ AS 'MODULE_PATHNAME';
-- agtype - edge
--
CREATE FUNCTION ag_catalog._agtype_build_edge(graphid, graphid, graphid,
- cstring, agtype)
+ agtype, agtype)
RETURNS agtype
LANGUAGE c
IMMUTABLE
CALLED ON NULL INPUT
PARALLEL SAFE
AS 'MODULE_PATHNAME';
+--
+-- vertex/edge to agtype cast functions
+--
+CREATE FUNCTION ag_catalog.vertex_to_agtype(vertex)
+ RETURNS agtype
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION ag_catalog.edge_to_agtype(edge)
+ RETURNS agtype
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+--
+-- Implicit casts from vertex/edge to agtype
+--
+CREATE CAST (vertex AS agtype)
+ WITH FUNCTION ag_catalog.vertex_to_agtype(vertex)
+AS IMPLICIT;
+
+CREATE CAST (edge AS agtype)
+ WITH FUNCTION ag_catalog.edge_to_agtype(edge)
+AS IMPLICIT;
+
+CREATE FUNCTION ag_catalog.vertex_to_json(vertex)
+ RETURNS json
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION ag_catalog.edge_to_json(edge)
+ RETURNS json
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE CAST (vertex AS json)
+ WITH FUNCTION ag_catalog.vertex_to_json(vertex);
+
+CREATE CAST (edge AS json)
+ WITH FUNCTION ag_catalog.edge_to_json(edge);
+
+CREATE FUNCTION ag_catalog.vertex_to_jsonb(vertex)
+ RETURNS jsonb
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION ag_catalog.edge_to_jsonb(edge)
+ RETURNS jsonb
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE CAST (vertex AS jsonb)
+ WITH FUNCTION ag_catalog.vertex_to_jsonb(vertex);
+
+CREATE CAST (edge AS jsonb)
+ WITH FUNCTION ag_catalog.edge_to_jsonb(edge);
+
+--
+-- Equality operators for vertex and edge (compare by id)
+--
+CREATE FUNCTION ag_catalog.vertex_eq(vertex, vertex)
+ RETURNS boolean
+ LANGUAGE sql
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS $$ SELECT $1.id = $2.id $$;
+
+CREATE OPERATOR = (
+ FUNCTION = ag_catalog.vertex_eq,
+ LEFTARG = vertex,
+ RIGHTARG = vertex,
+ COMMUTATOR = =,
+ NEGATOR = <>,
+ RESTRICT = eqsel,
+ JOIN = eqjoinsel
+);
+
+CREATE FUNCTION ag_catalog.vertex_ne(vertex, vertex)
+ RETURNS boolean
+ LANGUAGE sql
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS $$ SELECT $1.id <> $2.id $$;
+
+CREATE OPERATOR <> (
+ FUNCTION = ag_catalog.vertex_ne,
+ LEFTARG = vertex,
+ RIGHTARG = vertex,
+ COMMUTATOR = <>,
+ NEGATOR = =,
+ RESTRICT = neqsel,
+ JOIN = neqjoinsel
+);
+
+CREATE FUNCTION ag_catalog.edge_eq(edge, edge)
+ RETURNS boolean
+ LANGUAGE sql
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS $$ SELECT $1.id = $2.id $$;
+
+CREATE OPERATOR = (
+ FUNCTION = ag_catalog.edge_eq,
+ LEFTARG = edge,
+ RIGHTARG = edge,
+ COMMUTATOR = =,
+ NEGATOR = <>,
+ RESTRICT = eqsel,
+ JOIN = eqjoinsel
+);
Review Comment:
Same planning concern as `vertex = vertex`: the edge equality operator isn't
marked `HASHES`/`MERGES`, which can prevent hash/merge join selection when the
operator is referenced directly. Adding these flags matches the pattern used by
other equality operators in this extension (e.g., `graphid = graphid`).
##########
sql/agtype_graphid.sql:
##########
@@ -69,14 +96,163 @@ AS 'MODULE_PATHNAME';
-- agtype - edge
--
CREATE FUNCTION ag_catalog._agtype_build_edge(graphid, graphid, graphid,
- cstring, agtype)
+ agtype, agtype)
RETURNS agtype
LANGUAGE c
IMMUTABLE
CALLED ON NULL INPUT
PARALLEL SAFE
AS 'MODULE_PATHNAME';
+--
+-- vertex/edge to agtype cast functions
+--
+CREATE FUNCTION ag_catalog.vertex_to_agtype(vertex)
+ RETURNS agtype
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION ag_catalog.edge_to_agtype(edge)
+ RETURNS agtype
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+--
+-- Implicit casts from vertex/edge to agtype
+--
+CREATE CAST (vertex AS agtype)
+ WITH FUNCTION ag_catalog.vertex_to_agtype(vertex)
+AS IMPLICIT;
+
+CREATE CAST (edge AS agtype)
+ WITH FUNCTION ag_catalog.edge_to_agtype(edge)
+AS IMPLICIT;
+
+CREATE FUNCTION ag_catalog.vertex_to_json(vertex)
+ RETURNS json
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION ag_catalog.edge_to_json(edge)
+ RETURNS json
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE CAST (vertex AS json)
+ WITH FUNCTION ag_catalog.vertex_to_json(vertex);
+
+CREATE CAST (edge AS json)
+ WITH FUNCTION ag_catalog.edge_to_json(edge);
+
+CREATE FUNCTION ag_catalog.vertex_to_jsonb(vertex)
+ RETURNS jsonb
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION ag_catalog.edge_to_jsonb(edge)
+ RETURNS jsonb
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE CAST (vertex AS jsonb)
+ WITH FUNCTION ag_catalog.vertex_to_jsonb(vertex);
+
+CREATE CAST (edge AS jsonb)
+ WITH FUNCTION ag_catalog.edge_to_jsonb(edge);
+
+--
+-- Equality operators for vertex and edge (compare by id)
+--
+CREATE FUNCTION ag_catalog.vertex_eq(vertex, vertex)
+ RETURNS boolean
+ LANGUAGE sql
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS $$ SELECT $1.id = $2.id $$;
+
+CREATE OPERATOR = (
+ FUNCTION = ag_catalog.vertex_eq,
+ LEFTARG = vertex,
+ RIGHTARG = vertex,
+ COMMUTATOR = =,
+ NEGATOR = <>,
+ RESTRICT = eqsel,
+ JOIN = eqjoinsel
+);
Review Comment:
The vertex equality operator is intended to behave like an equality operator
for planning purposes, but it isn't marked `HASHES`/`MERGES` (unlike the
existing `graphid` `=` operator). Without these flags, the planner may miss
hash/merge join optimizations when the operator itself (not its inlined SQL
body) is used. Consider adding `HASHES, MERGES` here.
##########
sql/age_scalar.sql:
##########
@@ -93,6 +93,15 @@ RETURNS NULL ON NULL INPUT
PARALLEL SAFE
AS 'MODULE_PATHNAME';
+-- Helper function for optimized startNode/endNode
+CREATE FUNCTION ag_catalog._get_vertex_by_graphid(text, graphid)
+ RETURNS agtype
+ LANGUAGE c
+ STABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
Review Comment:
The upgrade script revokes PUBLIC access to
`ag_catalog._get_vertex_by_graphid(text, graphid)`, but the base install SQL
here does not. That makes privileges differ depending on whether the function
is created via fresh install vs upgrade. Consider adding the same REVOKE here
(or dropping it from the upgrade script) to keep behavior consistent.
##########
sql/agtype_graphid.sql:
##########
@@ -69,14 +96,163 @@ AS 'MODULE_PATHNAME';
-- agtype - edge
--
CREATE FUNCTION ag_catalog._agtype_build_edge(graphid, graphid, graphid,
- cstring, agtype)
+ agtype, agtype)
RETURNS agtype
LANGUAGE c
IMMUTABLE
CALLED ON NULL INPUT
PARALLEL SAFE
AS 'MODULE_PATHNAME';
+--
+-- vertex/edge to agtype cast functions
+--
+CREATE FUNCTION ag_catalog.vertex_to_agtype(vertex)
+ RETURNS agtype
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION ag_catalog.edge_to_agtype(edge)
+ RETURNS agtype
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+--
+-- Implicit casts from vertex/edge to agtype
+--
+CREATE CAST (vertex AS agtype)
+ WITH FUNCTION ag_catalog.vertex_to_agtype(vertex)
+AS IMPLICIT;
+
+CREATE CAST (edge AS agtype)
+ WITH FUNCTION ag_catalog.edge_to_agtype(edge)
+AS IMPLICIT;
+
+CREATE FUNCTION ag_catalog.vertex_to_json(vertex)
+ RETURNS json
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION ag_catalog.edge_to_json(edge)
+ RETURNS json
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE CAST (vertex AS json)
+ WITH FUNCTION ag_catalog.vertex_to_json(vertex);
+
+CREATE CAST (edge AS json)
+ WITH FUNCTION ag_catalog.edge_to_json(edge);
+
+CREATE FUNCTION ag_catalog.vertex_to_jsonb(vertex)
+ RETURNS jsonb
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION ag_catalog.edge_to_jsonb(edge)
+ RETURNS jsonb
+ LANGUAGE c
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+CREATE CAST (vertex AS jsonb)
+ WITH FUNCTION ag_catalog.vertex_to_jsonb(vertex);
+
+CREATE CAST (edge AS jsonb)
+ WITH FUNCTION ag_catalog.edge_to_jsonb(edge);
+
+--
+-- Equality operators for vertex and edge (compare by id)
+--
+CREATE FUNCTION ag_catalog.vertex_eq(vertex, vertex)
+ RETURNS boolean
+ LANGUAGE sql
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS $$ SELECT $1.id = $2.id $$;
+
+CREATE OPERATOR = (
+ FUNCTION = ag_catalog.vertex_eq,
+ LEFTARG = vertex,
+ RIGHTARG = vertex,
+ COMMUTATOR = =,
+ NEGATOR = <>,
+ RESTRICT = eqsel,
+ JOIN = eqjoinsel
+);
+
+CREATE FUNCTION ag_catalog.vertex_ne(vertex, vertex)
+ RETURNS boolean
+ LANGUAGE sql
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS $$ SELECT $1.id <> $2.id $$;
+
+CREATE OPERATOR <> (
+ FUNCTION = ag_catalog.vertex_ne,
+ LEFTARG = vertex,
+ RIGHTARG = vertex,
+ COMMUTATOR = <>,
+ NEGATOR = =,
+ RESTRICT = neqsel,
+ JOIN = neqjoinsel
+);
+
+CREATE FUNCTION ag_catalog.edge_eq(edge, edge)
+ RETURNS boolean
+ LANGUAGE sql
+ IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS $$ SELECT $1.id = $2.id $$;
+
+CREATE OPERATOR = (
+ FUNCTION = ag_catalog.edge_eq,
+ LEFTARG = edge,
+ RIGHTARG = edge,
+ COMMUTATOR = =,
+ NEGATOR = <>,
+ RESTRICT = eqsel,
+ JOIN = eqjoinsel
+);
Review Comment:
Same planning concern as `vertex = vertex`: the edge equality operator isn't
marked `HASHES`/`MERGES`, which can prevent hash/merge join selection when the
operator is referenced directly. Adding these flags matches the pattern used by
other equality operators in this extension (e.g., `graphid = graphid`).
##########
sql/age_scalar.sql:
##########
@@ -93,6 +93,15 @@ RETURNS NULL ON NULL INPUT
PARALLEL SAFE
AS 'MODULE_PATHNAME';
+-- Helper function for optimized startNode/endNode
+CREATE FUNCTION ag_catalog._get_vertex_by_graphid(text, graphid)
+ RETURNS agtype
+ LANGUAGE c
+ STABLE
+RETURNS NULL ON NULL INPUT
Review Comment:
The upgrade script revokes PUBLIC access to
`ag_catalog._get_vertex_by_graphid(text, graphid)`, but the base install SQL
here does not. That makes privileges differ depending on whether the function
is created via fresh install vs upgrade. Consider adding the same REVOKE here
(or dropping it from the upgrade script) to keep behavior consistent.
--
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]