Skip to content

Commit 6a3fe88

Browse files
committed
Optimize vertex/edge field access with direct array indexing
NOTE: This PR was created using AI tools and a human. Leverage deterministic key ordering from uniqueify_agtype_object() to access vertex/edge fields in O(1) instead of O(log n) binary search. Fields are sorted by key length, giving fixed positions: - Vertex: id(0), label(1), properties(2) - Edge: id(0), label(1), end_id(2), start_id(3), properties(4) Changes: - Add field index constants and accessor macros to agtype.h - Update age_id(), age_start_id(), age_end_id(), age_label(), age_properties() to use direct field access - Add fill_agtype_value_no_copy() for read-only scalar extraction without memory allocation - Add compare_agtype_scalar_containers() fast path for scalar comparison - Update hash_agtype_value(), equals_agtype_scalar_value(), and compare_agtype_scalar_values() to use direct field access macros - Add fast path in get_one_agtype_from_variadic_args() bypassing extract_variadic_args() for single argument case - Add comprehensive regression test (30 tests) Performance impact: Improves ORDER BY, hash joins, aggregations, and Cypher functions (id, start_id, end_id, label, properties) on vertices and edges. All previous regression tests were not impacted. Additional regression test added to enhance coverage. modified: Makefile new file: regress/expected/direct_field_access.out new file: regress/sql/direct_field_access.sql modified: src/backend/utils/adt/agtype.c modified: src/backend/utils/adt/agtype_util.c modified: src/include/utils/agtype.h
1 parent c979380 commit 6a3fe88

4 files changed

Lines changed: 450 additions & 29 deletions

File tree

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ REGRESS = scan \
112112
name_validation \
113113
jsonb_operators \
114114
list_comprehension \
115-
map_projection
115+
map_projection \
116+
direct_field_access
116117

117118
ifneq ($(EXTRA_TESTS),)
118119
REGRESS += $(EXTRA_TESTS)

src/backend/utils/adt/agtype.c

Lines changed: 118 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5409,10 +5409,24 @@ Datum age_id(PG_FUNCTION_ARGS)
54095409
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
54105410
errmsg("id() argument must be a vertex, an edge or null")));
54115411

5412-
agtv_result = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_object, "id");
5413-
5414-
Assert(agtv_result != NULL);
5415-
Assert(agtv_result->type = AGTV_INTEGER);
5412+
/*
5413+
* Direct field access optimization: id is at a fixed index for both
5414+
* vertex and edge objects due to key length sorting.
5415+
*/
5416+
if (agtv_object->type == AGTV_VERTEX)
5417+
{
5418+
agtv_result = AGTYPE_VERTEX_GET_ID(agtv_object);
5419+
}
5420+
else if (agtv_object->type == AGTV_EDGE)
5421+
{
5422+
agtv_result = AGTYPE_EDGE_GET_ID(agtv_object);
5423+
}
5424+
else
5425+
{
5426+
ereport(ERROR,
5427+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
5428+
errmsg("id() unexpected argument type")));
5429+
}
54165430

54175431
PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result));
54185432
}
@@ -5447,10 +5461,11 @@ Datum age_start_id(PG_FUNCTION_ARGS)
54475461
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
54485462
errmsg("start_id() argument must be an edge or null")));
54495463

5450-
agtv_result = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_object, "start_id");
5451-
5452-
Assert(agtv_result != NULL);
5453-
Assert(agtv_result->type = AGTV_INTEGER);
5464+
/*
5465+
* Direct field access optimization: start_id is at index 3 for edge
5466+
* objects due to key length sorting (id=0, label=1, end_id=2, start_id=3).
5467+
*/
5468+
agtv_result = AGTYPE_EDGE_GET_START_ID(agtv_object);
54545469

54555470
PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result));
54565471
}
@@ -5485,10 +5500,11 @@ Datum age_end_id(PG_FUNCTION_ARGS)
54855500
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
54865501
errmsg("end_id() argument must be an edge or null")));
54875502

5488-
agtv_result = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_object, "end_id");
5489-
5490-
Assert(agtv_result != NULL);
5491-
Assert(agtv_result->type = AGTV_INTEGER);
5503+
/*
5504+
* Direct field access optimization: end_id is at index 2 for edge
5505+
* objects due to key length sorting (id=0, label=1, end_id=2).
5506+
*/
5507+
agtv_result = AGTYPE_EDGE_GET_END_ID(agtv_object);
54925508

54935509
PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result));
54945510
}
@@ -6038,10 +6054,25 @@ Datum age_properties(PG_FUNCTION_ARGS)
60386054
errmsg("properties() argument must be a vertex, an edge or null")));
60396055
}
60406056

6041-
agtv_result = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_object, "properties");
6042-
6043-
Assert(agtv_result != NULL);
6044-
Assert(agtv_result->type = AGTV_OBJECT);
6057+
/*
6058+
* Direct field access optimization: properties is at index 2 for vertex
6059+
* (id=0, label=1, properties=2) and index 4 for edge (id=0, label=1,
6060+
* end_id=2, start_id=3, properties=4) due to key length sorting.
6061+
*/
6062+
if (agtv_object->type == AGTV_VERTEX)
6063+
{
6064+
agtv_result = AGTYPE_VERTEX_GET_PROPERTIES(agtv_object);
6065+
}
6066+
else if (agtv_object->type == AGTV_EDGE)
6067+
{
6068+
agtv_result = AGTYPE_EDGE_GET_PROPERTIES(agtv_object);
6069+
}
6070+
else
6071+
{
6072+
ereport(ERROR,
6073+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
6074+
errmsg("properties() unexpected argument type")));
6075+
}
60456076

60466077
PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result));
60476078
}
@@ -7170,8 +7201,24 @@ Datum age_label(PG_FUNCTION_ARGS)
71707201

71717202
}
71727203

7173-
/* extract the label agtype value from the vertex or edge */
7174-
label = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_value, "label");
7204+
/*
7205+
* Direct field access optimization: label is at a fixed index for both
7206+
* vertex and edge objects due to key length sorting.
7207+
*/
7208+
if (agtv_value->type == AGTV_VERTEX)
7209+
{
7210+
label = AGTYPE_VERTEX_GET_LABEL(agtv_value);
7211+
}
7212+
else if (agtv_value->type == AGTV_EDGE)
7213+
{
7214+
label = AGTYPE_EDGE_GET_LABEL(agtv_value);
7215+
}
7216+
else
7217+
{
7218+
ereport(ERROR,
7219+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
7220+
errmsg("label() unexpected argument type")));
7221+
}
71757222

71767223
PG_RETURN_POINTER(agtype_value_to_agtype(label));
71777224
}
@@ -10507,6 +10554,59 @@ agtype *get_one_agtype_from_variadic_args(FunctionCallInfo fcinfo,
1050710554
Oid *types = NULL;
1050810555
agtype *agtype_result = NULL;
1050910556

10557+
/*
10558+
* Fast path optimization: For non-variadic calls where the argument
10559+
* is already an agtype, we can avoid the overhead of extract_variadic_args
10560+
* which allocates three arrays. This is the common case for most agtype
10561+
* comparison and arithmetic operators.
10562+
*/
10563+
if (!get_fn_expr_variadic(fcinfo->flinfo))
10564+
{
10565+
int total_args = PG_NARGS();
10566+
int actual_nargs = total_args - variadic_offset;
10567+
10568+
/* Verify expected number of arguments */
10569+
if (actual_nargs != expected_nargs)
10570+
{
10571+
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
10572+
errmsg("number of args %d does not match expected %d",
10573+
actual_nargs, expected_nargs)));
10574+
}
10575+
10576+
/* Check for SQL NULL */
10577+
if (PG_ARGISNULL(variadic_offset))
10578+
{
10579+
return NULL;
10580+
}
10581+
10582+
/* Check if the argument is already an agtype */
10583+
if (get_fn_expr_argtype(fcinfo->flinfo, variadic_offset) == AGTYPEOID)
10584+
{
10585+
agtype_container *agtc;
10586+
10587+
agtype_result = DATUM_GET_AGTYPE_P(PG_GETARG_DATUM(variadic_offset));
10588+
agtc = &agtype_result->root;
10589+
10590+
/*
10591+
* Is this a scalar (scalars are stored as one element arrays)?
10592+
* If so, test for agtype NULL.
10593+
*/
10594+
if (AGTYPE_CONTAINER_IS_SCALAR(agtc) &&
10595+
AGTE_IS_NULL(agtc->children[0]))
10596+
{
10597+
return NULL;
10598+
}
10599+
10600+
return agtype_result;
10601+
}
10602+
10603+
/*
10604+
* Not an agtype, need to convert. Fall through to use
10605+
* extract_variadic_args for type conversion handling.
10606+
*/
10607+
}
10608+
10609+
/* Standard path using extract_variadic_args */
1051010610
nargs = extract_variadic_args(fcinfo, variadic_offset, false, &args, &types,
1051110611
&nulls);
1051210612
/* throw an error if the number of args is not the expected number */

0 commit comments

Comments
 (0)