This is an automated email from the ASF dual-hosted git repository.

mtaha pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/age.git


The following commit(s) were added to refs/heads/master by this push:
     new b9d09828 Optimize vertex/edge field access with direct array indexing 
(#2302)
b9d09828 is described below

commit b9d0982892306abff0013dd8f336e153684b02e9
Author: John Gemignani <[email protected]>
AuthorDate: Fri Jan 16 15:12:22 2026 -0800

    Optimize vertex/edge field access with direct array indexing (#2302)
    
    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
---
 Makefile                                 |   3 +-
 regress/expected/direct_field_access.out | 535 +++++++++++++++++++++++++++++++
 regress/sql/direct_field_access.sql      | 319 ++++++++++++++++++
 src/backend/utils/adt/agtype.c           | 136 ++++++--
 src/backend/utils/adt/agtype_util.c      | 237 +++++++++++++-
 src/include/utils/agtype.h               | 103 ++++++
 6 files changed, 1304 insertions(+), 29 deletions(-)

diff --git a/Makefile b/Makefile
index 17f2ed65..ffad7d6a 100644
--- a/Makefile
+++ b/Makefile
@@ -112,7 +112,8 @@ REGRESS = scan \
           name_validation \
           jsonb_operators \
           list_comprehension \
-          map_projection
+          map_projection \
+          direct_field_access
 
 ifneq ($(EXTRA_TESTS),)
   REGRESS += $(EXTRA_TESTS)
diff --git a/regress/expected/direct_field_access.out 
b/regress/expected/direct_field_access.out
new file mode 100644
index 00000000..0a059cdd
--- /dev/null
+++ b/regress/expected/direct_field_access.out
@@ -0,0 +1,535 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Direct Field Access Optimizations Test
+ *
+ * Tests for optimizations that directly access agtype fields without
+ * using the full iterator machinery or binary search:
+ *
+ * 1. fill_agtype_value_no_copy() - Read-only access without memory allocation
+ * 2. compare_agtype_scalar_containers() - Fast path for scalar comparisons
+ * 3. Direct pairs[0] access for vertex/edge id comparison
+ * 4. Fast path in get_one_agtype_from_variadic_args()
+ */
+LOAD 'age';
+SET search_path TO ag_catalog;
+SELECT create_graph('direct_access');
+NOTICE:  graph "direct_access" has been created
+ create_graph 
+--------------
+ 
+(1 row)
+
+--
+-- Section 1: Scalar Comparison Fast Path Tests
+--
+-- These tests exercise the compare_agtype_scalar_containers() fast path
+-- which uses fill_agtype_value_no_copy() for read-only comparisons.
+--
+-- Integer comparisons
+SELECT * FROM cypher('direct_access', $$
+    RETURN 1 < 2, 2 > 1, 1 = 1, 1 <> 2
+$$) AS (lt agtype, gt agtype, eq agtype, ne agtype);
+  lt  |  gt  |  eq  |  ne  
+------+------+------+------
+ true | true | true | true
+(1 row)
+
+SELECT * FROM cypher('direct_access', $$
+    RETURN 100 < 50, 100 > 50, 100 = 100, 100 <> 100
+$$) AS (lt agtype, gt agtype, eq agtype, ne agtype);
+  lt   |  gt  |  eq  |  ne   
+-------+------+------+-------
+ false | true | true | false
+(1 row)
+
+-- Float comparisons
+SELECT * FROM cypher('direct_access', $$
+    RETURN 1.5 < 2.5, 2.5 > 1.5, 1.5 = 1.5, 1.5 <> 2.5
+$$) AS (lt agtype, gt agtype, eq agtype, ne agtype);
+  lt  |  gt  |  eq  |  ne  
+------+------+------+------
+ true | true | true | true
+(1 row)
+
+-- String comparisons (tests no-copy string pointer)
+SELECT * FROM cypher('direct_access', $$
+    RETURN 'abc' < 'abd', 'abd' > 'abc', 'abc' = 'abc', 'abc' <> 'abd'
+$$) AS (lt agtype, gt agtype, eq agtype, ne agtype);
+  lt  |  gt  |  eq  |  ne  
+------+------+------+------
+ true | true | true | true
+(1 row)
+
+SELECT * FROM cypher('direct_access', $$
+    RETURN 'hello world' < 'hello worlds', 'test' > 'TEST'
+$$) AS (lt agtype, gt agtype);
+  lt  |  gt  
+------+------
+ true | true
+(1 row)
+
+-- Boolean comparisons
+SELECT * FROM cypher('direct_access', $$
+    RETURN false < true, true > false, true = true, false <> true
+$$) AS (lt agtype, gt agtype, eq agtype, ne agtype);
+  lt  |  gt  |  eq  |  ne  
+------+------+------+------
+ true | true | true | true
+(1 row)
+
+-- Null comparisons
+SELECT * FROM cypher('direct_access', $$
+    RETURN null = null, null <> null
+$$) AS (eq agtype, ne agtype);
+ eq | ne 
+----+----
+    | 
+(1 row)
+
+-- Mixed numeric type comparisons (integer vs float)
+SELECT * FROM cypher('direct_access', $$
+    RETURN 1 < 1.5, 2.0 > 1, 1.0 = 1
+$$) AS (lt agtype, gt agtype, eq agtype);
+  lt  |  gt  |  eq  
+------+------+------
+ true | true | true
+(1 row)
+
+-- Numeric type comparisons
+SELECT * FROM cypher('direct_access', $$
+    RETURN 1.234::numeric < 1.235::numeric,
+           1.235::numeric > 1.234::numeric,
+           1.234::numeric = 1.234::numeric
+$$) AS (lt agtype, gt agtype, eq agtype);
+  lt  |  gt  |  eq  
+------+------+------
+ true | true | true
+(1 row)
+
+--
+-- Section 2: ORDER BY Tests (exercises comparison fast path)
+--
+-- ORDER BY uses compare_agtype_containers_orderability which now has
+-- a fast path for scalar comparisons.
+--
+-- Integer ORDER BY
+SELECT * FROM cypher('direct_access', $$
+    UNWIND [5, 3, 8, 1, 9, 2, 7, 4, 6] AS n
+    RETURN n ORDER BY n
+$$) AS (n agtype);
+ n 
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+(9 rows)
+
+SELECT * FROM cypher('direct_access', $$
+    UNWIND [5, 3, 8, 1, 9, 2, 7, 4, 6] AS n
+    RETURN n ORDER BY n DESC
+$$) AS (n agtype);
+ n 
+---
+ 9
+ 8
+ 7
+ 6
+ 5
+ 4
+ 3
+ 2
+ 1
+(9 rows)
+
+-- String ORDER BY
+SELECT * FROM cypher('direct_access', $$
+    UNWIND ['banana', 'apple', 'cherry', 'date'] AS s
+    RETURN s ORDER BY s
+$$) AS (s agtype);
+    s     
+----------
+ "apple"
+ "banana"
+ "cherry"
+ "date"
+(4 rows)
+
+-- Float ORDER BY
+SELECT * FROM cypher('direct_access', $$
+    UNWIND [3.14, 2.71, 1.41, 1.73] AS f
+    RETURN f ORDER BY f
+$$) AS (f agtype);
+  f   
+------
+ 1.41
+ 1.73
+ 2.71
+ 3.14
+(4 rows)
+
+-- Boolean ORDER BY
+SELECT * FROM cypher('direct_access', $$
+    UNWIND [true, false, true, false] AS b
+    RETURN b ORDER BY b
+$$) AS (b agtype);
+   b   
+-------
+ false
+ false
+ true
+ true
+(4 rows)
+
+--
+-- Section 3: Vertex/Edge Direct ID Access Tests
+--
+-- These tests exercise the direct pairs[0] access optimization for
+-- extracting graphid from vertices and edges during comparison.
+--
+-- Create test data
+SELECT * FROM cypher('direct_access', $$
+    CREATE (a:Person {name: 'Alice', age: 30}),
+           (b:Person {name: 'Bob', age: 25}),
+           (c:Person {name: 'Charlie', age: 35}),
+           (d:Person {name: 'Diana', age: 28}),
+           (e:Person {name: 'Eve', age: 32}),
+           (a)-[:KNOWS {since: 2020}]->(b),
+           (b)-[:KNOWS {since: 2019}]->(c),
+           (c)-[:KNOWS {since: 2021}]->(d),
+           (d)-[:KNOWS {since: 2018}]->(e),
+           (e)-[:KNOWS {since: 2022}]->(a)
+$$) AS (result agtype);
+ result 
+--------
+(0 rows)
+
+-- Test max() on vertices (uses compare_agtype_scalar_values with AGTV_VERTEX)
+SELECT * FROM cypher('direct_access', $$
+    MATCH (p:Person)
+    RETURN max(p)
+$$) AS (max_vertex agtype);
+                                          max_vertex                           
               
+----------------------------------------------------------------------------------------------
+ {"id": 844424930131973, "label": "Person", "properties": {"age": 32, "name": 
"Eve"}}::vertex
+(1 row)
+
+-- Test min() on vertices
+SELECT * FROM cypher('direct_access', $$
+    MATCH (p:Person)
+    RETURN min(p)
+$$) AS (min_vertex agtype);
+                                           min_vertex                          
                 
+------------------------------------------------------------------------------------------------
+ {"id": 844424930131969, "label": "Person", "properties": {"age": 30, "name": 
"Alice"}}::vertex
+(1 row)
+
+-- Test max() on edges (uses compare_agtype_scalar_values with AGTV_EDGE)
+SELECT * FROM cypher('direct_access', $$
+    MATCH ()-[r:KNOWS]->()
+    RETURN max(r)
+$$) AS (max_edge agtype);
+                                                                max_edge       
                                                          
+-----------------------------------------------------------------------------------------------------------------------------------------
+ {"id": 1125899906842629, "label": "KNOWS", "end_id": 844424930131969, 
"start_id": 844424930131973, "properties": {"since": 2022}}::edge
+(1 row)
+
+-- Test min() on edges
+SELECT * FROM cypher('direct_access', $$
+    MATCH ()-[r:KNOWS]->()
+    RETURN min(r)
+$$) AS (min_edge agtype);
+                                                                min_edge       
                                                          
+-----------------------------------------------------------------------------------------------------------------------------------------
+ {"id": 1125899906842625, "label": "KNOWS", "end_id": 844424930131970, 
"start_id": 844424930131969, "properties": {"since": 2020}}::edge
+(1 row)
+
+-- ORDER BY on vertices (uses direct id comparison)
+SELECT * FROM cypher('direct_access', $$
+    MATCH (p:Person)
+    RETURN p.name ORDER BY p
+$$) AS (name agtype);
+   name    
+-----------
+ "Alice"
+ "Bob"
+ "Charlie"
+ "Diana"
+ "Eve"
+(5 rows)
+
+SELECT * FROM cypher('direct_access', $$
+    MATCH (p:Person)
+    RETURN p.name ORDER BY p DESC
+$$) AS (name agtype);
+   name    
+-----------
+ "Eve"
+ "Diana"
+ "Charlie"
+ "Bob"
+ "Alice"
+(5 rows)
+
+-- ORDER BY on edges
+SELECT * FROM cypher('direct_access', $$
+    MATCH ()-[r:KNOWS]->()
+    RETURN r.since ORDER BY r
+$$) AS (since agtype);
+ since 
+-------
+ 2020
+ 2019
+ 2021
+ 2018
+ 2022
+(5 rows)
+
+-- Vertex comparison in WHERE
+SELECT * FROM cypher('direct_access', $$
+    MATCH (a:Person), (b:Person)
+    WHERE a < b
+    RETURN a.name, b.name
+$$) AS (a_name agtype, b_name agtype);
+  a_name   |  b_name   
+-----------+-----------
+ "Alice"   | "Bob"
+ "Alice"   | "Charlie"
+ "Alice"   | "Diana"
+ "Alice"   | "Eve"
+ "Bob"     | "Charlie"
+ "Bob"     | "Diana"
+ "Bob"     | "Eve"
+ "Charlie" | "Diana"
+ "Charlie" | "Eve"
+ "Diana"   | "Eve"
+(10 rows)
+
+--
+-- Section 4: Fast Path for get_one_agtype_from_variadic_args
+--
+-- These tests exercise the fast path that bypasses extract_variadic_args
+-- when the argument is already agtype.
+--
+-- Direct agtype comparison operators (use the fast path)
+SELECT * FROM cypher('direct_access', $$
+    RETURN 42 = 42, 42 <> 43, 42 < 100, 42 > 10
+$$) AS (eq agtype, ne agtype, lt agtype, gt agtype);
+  eq  |  ne  |  lt  |  gt  
+------+------+------+------
+ true | true | true | true
+(1 row)
+
+-- Arithmetic operators (also use the fast path)
+SELECT * FROM cypher('direct_access', $$
+    RETURN 10 + 5, 10 - 5, 10 * 5, 10 / 5
+$$) AS (add agtype, sub agtype, mul agtype, div agtype);
+ add | sub | mul | div 
+-----+-----+-----+-----
+ 15  | 5   | 50  | 2
+(1 row)
+
+-- String functions that take agtype args
+SELECT * FROM cypher('direct_access', $$
+    RETURN toUpper('hello'), toLower('WORLD'), size('test')
+$$) AS (upper agtype, lower agtype, sz agtype);
+  upper  |  lower  | sz 
+---------+---------+----
+ "HELLO" | "world" | 4
+(1 row)
+
+-- Type checking functions
+SELECT * FROM cypher('direct_access', $$
+    RETURN toInteger('42'), toFloat('3.14'), toString(42)
+$$) AS (int_val agtype, float_val agtype, str_val agtype);
+ int_val | float_val | str_val 
+---------+-----------+---------
+ 42      | 3.14      | "42"
+(1 row)
+
+--
+-- Section 5: Direct Field Access for Accessor Functions
+--
+-- These tests exercise the direct field access macros in id(), start_id(),
+-- end_id(), label(), and properties() functions.
+--
+-- Test id() on vertices (uses AGTYPE_VERTEX_GET_ID macro - index 0)
+SELECT * FROM cypher('direct_access', $$
+    MATCH (p:Person {name: 'Alice'})
+    RETURN id(p)
+$$) AS (vertex_id agtype);
+    vertex_id    
+-----------------
+ 844424930131969
+(1 row)
+
+-- Test id() on edges (uses AGTYPE_EDGE_GET_ID macro - index 0)
+SELECT * FROM cypher('direct_access', $$
+    MATCH (a:Person {name: 'Alice'})-[r:KNOWS]->(b:Person {name: 'Bob'})
+    RETURN id(r)
+$$) AS (edge_id agtype);
+     edge_id      
+------------------
+ 1125899906842625
+(1 row)
+
+-- Test start_id() on edges (uses AGTYPE_EDGE_GET_START_ID macro - index 3)
+SELECT * FROM cypher('direct_access', $$
+    MATCH (a:Person {name: 'Alice'})-[r:KNOWS]->(b:Person {name: 'Bob'})
+    RETURN start_id(r), id(a)
+$$) AS (start_id agtype, alice_id agtype);
+    start_id     |    alice_id     
+-----------------+-----------------
+ 844424930131969 | 844424930131969
+(1 row)
+
+-- Test end_id() on edges (uses AGTYPE_EDGE_GET_END_ID macro - index 2)
+SELECT * FROM cypher('direct_access', $$
+    MATCH (a:Person {name: 'Alice'})-[r:KNOWS]->(b:Person {name: 'Bob'})
+    RETURN end_id(r), id(b)
+$$) AS (end_id agtype, bob_id agtype);
+     end_id      |     bob_id      
+-----------------+-----------------
+ 844424930131970 | 844424930131970
+(1 row)
+
+-- Test label() on vertices (uses AGTYPE_VERTEX_GET_LABEL macro - index 1)
+SELECT * FROM cypher('direct_access', $$
+    MATCH (p:Person {name: 'Alice'})
+    RETURN label(p)
+$$) AS (vertex_label agtype);
+ vertex_label 
+--------------
+ "Person"
+(1 row)
+
+-- Test label() on edges (uses AGTYPE_EDGE_GET_LABEL macro - index 1)
+SELECT * FROM cypher('direct_access', $$
+    MATCH ()-[r:KNOWS]->()
+    RETURN DISTINCT label(r)
+$$) AS (edge_label agtype);
+ edge_label 
+------------
+ "KNOWS"
+(1 row)
+
+-- Test properties() on vertices (uses AGTYPE_VERTEX_GET_PROPERTIES macro - 
index 2)
+SELECT * FROM cypher('direct_access', $$
+    MATCH (p:Person {name: 'Alice'})
+    RETURN properties(p)
+$$) AS (vertex_props agtype);
+         vertex_props         
+------------------------------
+ {"age": 30, "name": "Alice"}
+(1 row)
+
+-- Test properties() on edges (uses AGTYPE_EDGE_GET_PROPERTIES macro - index 4)
+SELECT * FROM cypher('direct_access', $$
+    MATCH (a:Person {name: 'Alice'})-[r:KNOWS]->(b:Person {name: 'Bob'})
+    RETURN properties(r)
+$$) AS (edge_props agtype);
+   edge_props    
+-----------------
+ {"since": 2020}
+(1 row)
+
+-- Combined accessor test - verify all fields are accessible
+SELECT * FROM cypher('direct_access', $$
+    MATCH (a:Person {name: 'Alice'})-[r:KNOWS]->(b:Person)
+    RETURN id(a), label(a), properties(a).name,
+           id(r), start_id(r), end_id(r), label(r), properties(r).since,
+           id(b), label(b), properties(b).name
+$$) AS (a_id agtype, a_label agtype, a_name agtype,
+        r_id agtype, r_start agtype, r_end agtype, r_label agtype, r_since 
agtype,
+        b_id agtype, b_label agtype, b_name agtype);
+      a_id       | a_label  | a_name  |       r_id       |     r_start     |   
   r_end      | r_label | r_since |      b_id       | b_label  | b_name 
+-----------------+----------+---------+------------------+-----------------+-----------------+---------+---------+-----------------+----------+--------
+ 844424930131969 | "Person" | "Alice" | 1125899906842625 | 844424930131969 | 
844424930131970 | "KNOWS" | 2020    | 844424930131970 | "Person" | "Bob"
+(1 row)
+
+--
+-- Section 6: Mixed Comparisons and Edge Cases
+--
+-- Array comparisons (should NOT use scalar fast path)
+SELECT * FROM cypher('direct_access', $$
+    RETURN [1,2,3] = [1,2,3], [1,2,3] < [1,2,4]
+$$) AS (eq agtype, lt agtype);
+  eq  |  lt  
+------+------
+ true | true
+(1 row)
+
+-- Object comparisons (should NOT use scalar fast path)
+SELECT * FROM cypher('direct_access', $$
+    RETURN {a:1, b:2} = {a:1, b:2}
+$$) AS (eq agtype);
+  eq  
+------
+ true
+(1 row)
+
+-- Large integer comparisons
+SELECT * FROM cypher('direct_access', $$
+    RETURN 9223372036854775807 > 9223372036854775806,
+           -9223372036854775808 < -9223372036854775807
+$$) AS (big_gt agtype, neg_lt agtype);
+ big_gt | neg_lt 
+--------+--------
+ true   | true
+(1 row)
+
+-- Empty string comparison
+SELECT * FROM cypher('direct_access', $$
+    RETURN '' < 'a', '' = ''
+$$) AS (lt agtype, eq agtype);
+  lt  |  eq  
+------+------
+ true | true
+(1 row)
+
+-- Special float values
+SELECT * FROM cypher('direct_access', $$
+    RETURN 0.0 = -0.0
+$$) AS (zero_eq agtype);
+ zero_eq 
+---------
+ true
+(1 row)
+
+--
+-- Cleanup
+--
+SELECT drop_graph('direct_access', true);
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table direct_access._ag_label_vertex
+drop cascades to table direct_access._ag_label_edge
+drop cascades to table direct_access."Person"
+drop cascades to table direct_access."KNOWS"
+NOTICE:  graph "direct_access" has been dropped
+ drop_graph 
+------------
+ 
+(1 row)
+
diff --git a/regress/sql/direct_field_access.sql 
b/regress/sql/direct_field_access.sql
new file mode 100644
index 00000000..c8060be4
--- /dev/null
+++ b/regress/sql/direct_field_access.sql
@@ -0,0 +1,319 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Direct Field Access Optimizations Test
+ *
+ * Tests for optimizations that directly access agtype fields without
+ * using the full iterator machinery or binary search:
+ *
+ * 1. fill_agtype_value_no_copy() - Read-only access without memory allocation
+ * 2. compare_agtype_scalar_containers() - Fast path for scalar comparisons
+ * 3. Direct pairs[0] access for vertex/edge id comparison
+ * 4. Fast path in get_one_agtype_from_variadic_args()
+ */
+
+LOAD 'age';
+SET search_path TO ag_catalog;
+
+SELECT create_graph('direct_access');
+
+--
+-- Section 1: Scalar Comparison Fast Path Tests
+--
+-- These tests exercise the compare_agtype_scalar_containers() fast path
+-- which uses fill_agtype_value_no_copy() for read-only comparisons.
+--
+
+-- Integer comparisons
+SELECT * FROM cypher('direct_access', $$
+    RETURN 1 < 2, 2 > 1, 1 = 1, 1 <> 2
+$$) AS (lt agtype, gt agtype, eq agtype, ne agtype);
+
+SELECT * FROM cypher('direct_access', $$
+    RETURN 100 < 50, 100 > 50, 100 = 100, 100 <> 100
+$$) AS (lt agtype, gt agtype, eq agtype, ne agtype);
+
+-- Float comparisons
+SELECT * FROM cypher('direct_access', $$
+    RETURN 1.5 < 2.5, 2.5 > 1.5, 1.5 = 1.5, 1.5 <> 2.5
+$$) AS (lt agtype, gt agtype, eq agtype, ne agtype);
+
+-- String comparisons (tests no-copy string pointer)
+SELECT * FROM cypher('direct_access', $$
+    RETURN 'abc' < 'abd', 'abd' > 'abc', 'abc' = 'abc', 'abc' <> 'abd'
+$$) AS (lt agtype, gt agtype, eq agtype, ne agtype);
+
+SELECT * FROM cypher('direct_access', $$
+    RETURN 'hello world' < 'hello worlds', 'test' > 'TEST'
+$$) AS (lt agtype, gt agtype);
+
+-- Boolean comparisons
+SELECT * FROM cypher('direct_access', $$
+    RETURN false < true, true > false, true = true, false <> true
+$$) AS (lt agtype, gt agtype, eq agtype, ne agtype);
+
+-- Null comparisons
+SELECT * FROM cypher('direct_access', $$
+    RETURN null = null, null <> null
+$$) AS (eq agtype, ne agtype);
+
+-- Mixed numeric type comparisons (integer vs float)
+SELECT * FROM cypher('direct_access', $$
+    RETURN 1 < 1.5, 2.0 > 1, 1.0 = 1
+$$) AS (lt agtype, gt agtype, eq agtype);
+
+-- Numeric type comparisons
+SELECT * FROM cypher('direct_access', $$
+    RETURN 1.234::numeric < 1.235::numeric,
+           1.235::numeric > 1.234::numeric,
+           1.234::numeric = 1.234::numeric
+$$) AS (lt agtype, gt agtype, eq agtype);
+
+--
+-- Section 2: ORDER BY Tests (exercises comparison fast path)
+--
+-- ORDER BY uses compare_agtype_containers_orderability which now has
+-- a fast path for scalar comparisons.
+--
+
+-- Integer ORDER BY
+SELECT * FROM cypher('direct_access', $$
+    UNWIND [5, 3, 8, 1, 9, 2, 7, 4, 6] AS n
+    RETURN n ORDER BY n
+$$) AS (n agtype);
+
+SELECT * FROM cypher('direct_access', $$
+    UNWIND [5, 3, 8, 1, 9, 2, 7, 4, 6] AS n
+    RETURN n ORDER BY n DESC
+$$) AS (n agtype);
+
+-- String ORDER BY
+SELECT * FROM cypher('direct_access', $$
+    UNWIND ['banana', 'apple', 'cherry', 'date'] AS s
+    RETURN s ORDER BY s
+$$) AS (s agtype);
+
+-- Float ORDER BY
+SELECT * FROM cypher('direct_access', $$
+    UNWIND [3.14, 2.71, 1.41, 1.73] AS f
+    RETURN f ORDER BY f
+$$) AS (f agtype);
+
+-- Boolean ORDER BY
+SELECT * FROM cypher('direct_access', $$
+    UNWIND [true, false, true, false] AS b
+    RETURN b ORDER BY b
+$$) AS (b agtype);
+
+--
+-- Section 3: Vertex/Edge Direct ID Access Tests
+--
+-- These tests exercise the direct pairs[0] access optimization for
+-- extracting graphid from vertices and edges during comparison.
+--
+
+-- Create test data
+SELECT * FROM cypher('direct_access', $$
+    CREATE (a:Person {name: 'Alice', age: 30}),
+           (b:Person {name: 'Bob', age: 25}),
+           (c:Person {name: 'Charlie', age: 35}),
+           (d:Person {name: 'Diana', age: 28}),
+           (e:Person {name: 'Eve', age: 32}),
+           (a)-[:KNOWS {since: 2020}]->(b),
+           (b)-[:KNOWS {since: 2019}]->(c),
+           (c)-[:KNOWS {since: 2021}]->(d),
+           (d)-[:KNOWS {since: 2018}]->(e),
+           (e)-[:KNOWS {since: 2022}]->(a)
+$$) AS (result agtype);
+
+-- Test max() on vertices (uses compare_agtype_scalar_values with AGTV_VERTEX)
+SELECT * FROM cypher('direct_access', $$
+    MATCH (p:Person)
+    RETURN max(p)
+$$) AS (max_vertex agtype);
+
+-- Test min() on vertices
+SELECT * FROM cypher('direct_access', $$
+    MATCH (p:Person)
+    RETURN min(p)
+$$) AS (min_vertex agtype);
+
+-- Test max() on edges (uses compare_agtype_scalar_values with AGTV_EDGE)
+SELECT * FROM cypher('direct_access', $$
+    MATCH ()-[r:KNOWS]->()
+    RETURN max(r)
+$$) AS (max_edge agtype);
+
+-- Test min() on edges
+SELECT * FROM cypher('direct_access', $$
+    MATCH ()-[r:KNOWS]->()
+    RETURN min(r)
+$$) AS (min_edge agtype);
+
+-- ORDER BY on vertices (uses direct id comparison)
+SELECT * FROM cypher('direct_access', $$
+    MATCH (p:Person)
+    RETURN p.name ORDER BY p
+$$) AS (name agtype);
+
+SELECT * FROM cypher('direct_access', $$
+    MATCH (p:Person)
+    RETURN p.name ORDER BY p DESC
+$$) AS (name agtype);
+
+-- ORDER BY on edges
+SELECT * FROM cypher('direct_access', $$
+    MATCH ()-[r:KNOWS]->()
+    RETURN r.since ORDER BY r
+$$) AS (since agtype);
+
+-- Vertex comparison in WHERE
+SELECT * FROM cypher('direct_access', $$
+    MATCH (a:Person), (b:Person)
+    WHERE a < b
+    RETURN a.name, b.name
+$$) AS (a_name agtype, b_name agtype);
+
+--
+-- Section 4: Fast Path for get_one_agtype_from_variadic_args
+--
+-- These tests exercise the fast path that bypasses extract_variadic_args
+-- when the argument is already agtype.
+--
+
+-- Direct agtype comparison operators (use the fast path)
+SELECT * FROM cypher('direct_access', $$
+    RETURN 42 = 42, 42 <> 43, 42 < 100, 42 > 10
+$$) AS (eq agtype, ne agtype, lt agtype, gt agtype);
+
+-- Arithmetic operators (also use the fast path)
+SELECT * FROM cypher('direct_access', $$
+    RETURN 10 + 5, 10 - 5, 10 * 5, 10 / 5
+$$) AS (add agtype, sub agtype, mul agtype, div agtype);
+
+-- String functions that take agtype args
+SELECT * FROM cypher('direct_access', $$
+    RETURN toUpper('hello'), toLower('WORLD'), size('test')
+$$) AS (upper agtype, lower agtype, sz agtype);
+
+-- Type checking functions
+SELECT * FROM cypher('direct_access', $$
+    RETURN toInteger('42'), toFloat('3.14'), toString(42)
+$$) AS (int_val agtype, float_val agtype, str_val agtype);
+
+--
+-- Section 5: Direct Field Access for Accessor Functions
+--
+-- These tests exercise the direct field access macros in id(), start_id(),
+-- end_id(), label(), and properties() functions.
+--
+
+-- Test id() on vertices (uses AGTYPE_VERTEX_GET_ID macro - index 0)
+SELECT * FROM cypher('direct_access', $$
+    MATCH (p:Person {name: 'Alice'})
+    RETURN id(p)
+$$) AS (vertex_id agtype);
+
+-- Test id() on edges (uses AGTYPE_EDGE_GET_ID macro - index 0)
+SELECT * FROM cypher('direct_access', $$
+    MATCH (a:Person {name: 'Alice'})-[r:KNOWS]->(b:Person {name: 'Bob'})
+    RETURN id(r)
+$$) AS (edge_id agtype);
+
+-- Test start_id() on edges (uses AGTYPE_EDGE_GET_START_ID macro - index 3)
+SELECT * FROM cypher('direct_access', $$
+    MATCH (a:Person {name: 'Alice'})-[r:KNOWS]->(b:Person {name: 'Bob'})
+    RETURN start_id(r), id(a)
+$$) AS (start_id agtype, alice_id agtype);
+
+-- Test end_id() on edges (uses AGTYPE_EDGE_GET_END_ID macro - index 2)
+SELECT * FROM cypher('direct_access', $$
+    MATCH (a:Person {name: 'Alice'})-[r:KNOWS]->(b:Person {name: 'Bob'})
+    RETURN end_id(r), id(b)
+$$) AS (end_id agtype, bob_id agtype);
+
+-- Test label() on vertices (uses AGTYPE_VERTEX_GET_LABEL macro - index 1)
+SELECT * FROM cypher('direct_access', $$
+    MATCH (p:Person {name: 'Alice'})
+    RETURN label(p)
+$$) AS (vertex_label agtype);
+
+-- Test label() on edges (uses AGTYPE_EDGE_GET_LABEL macro - index 1)
+SELECT * FROM cypher('direct_access', $$
+    MATCH ()-[r:KNOWS]->()
+    RETURN DISTINCT label(r)
+$$) AS (edge_label agtype);
+
+-- Test properties() on vertices (uses AGTYPE_VERTEX_GET_PROPERTIES macro - 
index 2)
+SELECT * FROM cypher('direct_access', $$
+    MATCH (p:Person {name: 'Alice'})
+    RETURN properties(p)
+$$) AS (vertex_props agtype);
+
+-- Test properties() on edges (uses AGTYPE_EDGE_GET_PROPERTIES macro - index 4)
+SELECT * FROM cypher('direct_access', $$
+    MATCH (a:Person {name: 'Alice'})-[r:KNOWS]->(b:Person {name: 'Bob'})
+    RETURN properties(r)
+$$) AS (edge_props agtype);
+
+-- Combined accessor test - verify all fields are accessible
+SELECT * FROM cypher('direct_access', $$
+    MATCH (a:Person {name: 'Alice'})-[r:KNOWS]->(b:Person)
+    RETURN id(a), label(a), properties(a).name,
+           id(r), start_id(r), end_id(r), label(r), properties(r).since,
+           id(b), label(b), properties(b).name
+$$) AS (a_id agtype, a_label agtype, a_name agtype,
+        r_id agtype, r_start agtype, r_end agtype, r_label agtype, r_since 
agtype,
+        b_id agtype, b_label agtype, b_name agtype);
+
+--
+-- Section 6: Mixed Comparisons and Edge Cases
+--
+
+-- Array comparisons (should NOT use scalar fast path)
+SELECT * FROM cypher('direct_access', $$
+    RETURN [1,2,3] = [1,2,3], [1,2,3] < [1,2,4]
+$$) AS (eq agtype, lt agtype);
+
+-- Object comparisons (should NOT use scalar fast path)
+SELECT * FROM cypher('direct_access', $$
+    RETURN {a:1, b:2} = {a:1, b:2}
+$$) AS (eq agtype);
+
+-- Large integer comparisons
+SELECT * FROM cypher('direct_access', $$
+    RETURN 9223372036854775807 > 9223372036854775806,
+           -9223372036854775808 < -9223372036854775807
+$$) AS (big_gt agtype, neg_lt agtype);
+
+-- Empty string comparison
+SELECT * FROM cypher('direct_access', $$
+    RETURN '' < 'a', '' = ''
+$$) AS (lt agtype, eq agtype);
+
+-- Special float values
+SELECT * FROM cypher('direct_access', $$
+    RETURN 0.0 = -0.0
+$$) AS (zero_eq agtype);
+
+--
+-- Cleanup
+--
+SELECT drop_graph('direct_access', true);
diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c
index 02fc3221..f2458a30 100644
--- a/src/backend/utils/adt/agtype.c
+++ b/src/backend/utils/adt/agtype.c
@@ -5409,10 +5409,24 @@ Datum age_id(PG_FUNCTION_ARGS)
         ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                         errmsg("id() argument must be a vertex, an edge or 
null")));
 
-    agtv_result = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_object, "id");
-
-    Assert(agtv_result != NULL);
-    Assert(agtv_result->type = AGTV_INTEGER);
+    /*
+     * Direct field access optimization: id is at a fixed index for both
+     * vertex and edge objects due to key length sorting.
+     */
+    if (agtv_object->type == AGTV_VERTEX)
+    {
+        agtv_result = AGTYPE_VERTEX_GET_ID(agtv_object);
+    }
+    else if (agtv_object->type == AGTV_EDGE)
+    {
+        agtv_result = AGTYPE_EDGE_GET_ID(agtv_object);
+    }
+    else
+    {
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                 errmsg("id() unexpected argument type")));
+    }
 
     PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result));
 }
@@ -5447,10 +5461,11 @@ Datum age_start_id(PG_FUNCTION_ARGS)
         ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                         errmsg("start_id() argument must be an edge or 
null")));
 
-    agtv_result = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_object, "start_id");
-
-    Assert(agtv_result != NULL);
-    Assert(agtv_result->type = AGTV_INTEGER);
+    /*
+     * Direct field access optimization: start_id is at index 3 for edge
+     * objects due to key length sorting (id=0, label=1, end_id=2, start_id=3).
+     */
+    agtv_result = AGTYPE_EDGE_GET_START_ID(agtv_object);
 
     PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result));
 }
@@ -5485,10 +5500,11 @@ Datum age_end_id(PG_FUNCTION_ARGS)
         ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                         errmsg("end_id() argument must be an edge or null")));
 
-    agtv_result = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_object, "end_id");
-
-    Assert(agtv_result != NULL);
-    Assert(agtv_result->type = AGTV_INTEGER);
+    /*
+     * Direct field access optimization: end_id is at index 2 for edge
+     * objects due to key length sorting (id=0, label=1, end_id=2).
+     */
+    agtv_result = AGTYPE_EDGE_GET_END_ID(agtv_object);
 
     PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result));
 }
@@ -6038,10 +6054,25 @@ Datum age_properties(PG_FUNCTION_ARGS)
                         errmsg("properties() argument must be a vertex, an 
edge or null")));
     }
 
-    agtv_result = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_object, "properties");
-
-    Assert(agtv_result != NULL);
-    Assert(agtv_result->type = AGTV_OBJECT);
+    /*
+     * Direct field access optimization: properties is at index 2 for vertex
+     * (id=0, label=1, properties=2) and index 4 for edge (id=0, label=1,
+     * end_id=2, start_id=3, properties=4) due to key length sorting.
+     */
+    if (agtv_object->type == AGTV_VERTEX)
+    {
+        agtv_result = AGTYPE_VERTEX_GET_PROPERTIES(agtv_object);
+    }
+    else if (agtv_object->type == AGTV_EDGE)
+    {
+        agtv_result = AGTYPE_EDGE_GET_PROPERTIES(agtv_object);
+    }
+    else
+    {
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                 errmsg("properties() unexpected argument type")));
+    }
 
     PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result));
 }
@@ -7170,8 +7201,24 @@ Datum age_label(PG_FUNCTION_ARGS)
 
     }
 
-    /* extract the label agtype value from the vertex or edge */
-    label = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_value, "label");
+    /*
+     * Direct field access optimization: label is at a fixed index for both
+     * vertex and edge objects due to key length sorting.
+     */
+    if (agtv_value->type == AGTV_VERTEX)
+    {
+        label = AGTYPE_VERTEX_GET_LABEL(agtv_value);
+    }
+    else if (agtv_value->type == AGTV_EDGE)
+    {
+        label = AGTYPE_EDGE_GET_LABEL(agtv_value);
+    }
+    else
+    {
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                 errmsg("label() unexpected argument type")));
+    }
 
     PG_RETURN_POINTER(agtype_value_to_agtype(label));
 }
@@ -10507,6 +10554,59 @@ agtype 
*get_one_agtype_from_variadic_args(FunctionCallInfo fcinfo,
     Oid *types = NULL;
     agtype *agtype_result = NULL;
 
+    /*
+     * Fast path optimization: For non-variadic calls where the argument
+     * is already an agtype, we can avoid the overhead of extract_variadic_args
+     * which allocates three arrays. This is the common case for most agtype
+     * comparison and arithmetic operators.
+     */
+    if (!get_fn_expr_variadic(fcinfo->flinfo))
+    {
+        int total_args = PG_NARGS();
+        int actual_nargs = total_args - variadic_offset;
+
+        /* Verify expected number of arguments */
+        if (actual_nargs != expected_nargs)
+        {
+            ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                            errmsg("number of args %d does not match expected 
%d",
+                                   actual_nargs, expected_nargs)));
+        }
+
+        /* Check for SQL NULL */
+        if (PG_ARGISNULL(variadic_offset))
+        {
+            return NULL;
+        }
+
+        /* Check if the argument is already an agtype */
+        if (get_fn_expr_argtype(fcinfo->flinfo, variadic_offset) == AGTYPEOID)
+        {
+            agtype_container *agtc;
+
+            agtype_result = 
DATUM_GET_AGTYPE_P(PG_GETARG_DATUM(variadic_offset));
+            agtc = &agtype_result->root;
+
+            /*
+             * Is this a scalar (scalars are stored as one element arrays)?
+             * If so, test for agtype NULL.
+             */
+            if (AGTYPE_CONTAINER_IS_SCALAR(agtc) &&
+                AGTE_IS_NULL(agtc->children[0]))
+            {
+                return NULL;
+            }
+
+            return agtype_result;
+        }
+
+        /*
+         * Not an agtype, need to convert. Fall through to use
+         * extract_variadic_args for type conversion handling.
+         */
+    }
+
+    /* Standard path using extract_variadic_args */
     nargs = extract_variadic_args(fcinfo, variadic_offset, false, &args, 
&types,
                                   &nulls);
     /* throw an error if the number of args is not the expected number */
diff --git a/src/backend/utils/adt/agtype_util.c 
b/src/backend/utils/adt/agtype_util.c
index 01a965cd..b3972341 100644
--- a/src/backend/utils/adt/agtype_util.c
+++ b/src/backend/utils/adt/agtype_util.c
@@ -41,6 +41,14 @@
 
 #include "utils/agtype_ext.h"
 
+/*
+ * Extended type header macros - must match definitions in agtype_ext.c.
+ * These are used for deserializing extended agtype values (INTEGER, FLOAT,
+ * VERTEX, EDGE, PATH) from their binary representation.
+ */
+#define AGT_HEADER_TYPE uint32
+#define AGT_HEADER_SIZE sizeof(AGT_HEADER_TYPE)
+
 /*
  * Maximum number of elements in an array (or key/value pairs in an object).
  * This is limited by two things: the size of the agtentry array must fit
@@ -56,6 +64,11 @@
 static void fill_agtype_value(agtype_container *container, int index,
                               char *base_addr, uint32 offset,
                               agtype_value *result);
+static void fill_agtype_value_no_copy(agtype_container *container, int index,
+                                      char *base_addr, uint32 offset,
+                                      agtype_value *result);
+static int compare_agtype_scalar_containers(agtype_container *a,
+                                            agtype_container *b);
 static bool equals_agtype_scalar_value(agtype_value *a, agtype_value *b);
 static agtype *convert_to_agtype(agtype_value *val);
 static void convert_agtype_value(StringInfo buffer, agtentry *header,
@@ -264,6 +277,24 @@ int 
compare_agtype_containers_orderability(agtype_container *a,
     agtype_iterator *itb;
     int res = 0;
 
+    /*
+     * Fast path optimization for scalar values.
+     *
+     * The most common case in ORDER BY and comparison operations is comparing
+     * scalar values (integers, strings, floats, etc.). For these cases, we can
+     * avoid the overhead of the full iterator machinery by directly extracting
+     * and comparing the scalar values.
+     *
+     * This provides significant performance improvement because:
+     * 1. We avoid allocating two agtype_iterator structures
+     * 2. We avoid the iterator state machine overhead
+     * 3. We use no-copy extraction where possible
+     */
+    if (AGTYPE_CONTAINER_IS_SCALAR(a) && AGTYPE_CONTAINER_IS_SCALAR(b))
+    {
+        return compare_agtype_scalar_containers(a, b);
+    }
+
     ita = agtype_iterator_init(a);
     itb = agtype_iterator_init(b);
 
@@ -751,6 +782,173 @@ static void fill_agtype_value(agtype_container 
*container, int index,
     }
 }
 
+/*
+ * A helper function to fill in an agtype_value WITHOUT making deep copies.
+ * This is used for read-only comparison operations where the agtype_value
+ * will not outlive the container data. The caller MUST NOT free the
+ * agtype_value content or use it after the container is freed.
+ *
+ * This function provides significant performance improvements for comparison
+ * operations by avoiding palloc/memcpy for strings and numerics.
+ *
+ * Note: For AGTV_STRING, val.string.val points directly into container data.
+ * Note: For AGTV_NUMERIC, val.numeric points directly into container data.
+ * Note: Extended types (VERTEX, EDGE, PATH) still require deserialization,
+ *       so they use the standard fill_agtype_value path.
+ */
+static void fill_agtype_value_no_copy(agtype_container *container, int index,
+                                      char *base_addr, uint32 offset,
+                                      agtype_value *result)
+{
+    agtentry entry = container->children[index];
+
+    if (AGTE_IS_NULL(entry))
+    {
+        result->type = AGTV_NULL;
+    }
+    else if (AGTE_IS_STRING(entry))
+    {
+        result->type = AGTV_STRING;
+        /* Point directly into the container data - no copy */
+        result->val.string.val = base_addr + offset;
+        result->val.string.len = get_agtype_length(container, index);
+    }
+    else if (AGTE_IS_NUMERIC(entry))
+    {
+        result->type = AGTV_NUMERIC;
+        /* Point directly into the container data - no copy */
+        result->val.numeric = (Numeric)(base_addr + INTALIGN(offset));
+    }
+    else if (AGTE_IS_AGTYPE(entry))
+    {
+        /*
+         * For extended types (INTEGER, FLOAT, VERTEX, EDGE, PATH), we need
+         * to deserialize. INTEGER and FLOAT don't allocate, but composite
+         * types (VERTEX, EDGE, PATH) do. For simple scalar comparisons,
+         * we handle INTEGER and FLOAT directly here.
+         */
+        char *base = base_addr + INTALIGN(offset);
+        AGT_HEADER_TYPE agt_header = *((AGT_HEADER_TYPE *)base);
+
+        switch (agt_header)
+        {
+        case AGT_HEADER_INTEGER:
+            result->type = AGTV_INTEGER;
+            result->val.int_value = *((int64 *)(base + AGT_HEADER_SIZE));
+            break;
+
+        case AGT_HEADER_FLOAT:
+            result->type = AGTV_FLOAT;
+            result->val.float_value = *((float8 *)(base + AGT_HEADER_SIZE));
+            break;
+
+        default:
+            /*
+             * For VERTEX, EDGE, PATH - use standard deserialization.
+             * These are composite types that require full parsing.
+             */
+            ag_deserialize_extended_type(base_addr, offset, result);
+            break;
+        }
+    }
+    else if (AGTE_IS_BOOL_TRUE(entry))
+    {
+        result->type = AGTV_BOOL;
+        result->val.boolean = true;
+    }
+    else if (AGTE_IS_BOOL_FALSE(entry))
+    {
+        result->type = AGTV_BOOL;
+        result->val.boolean = false;
+    }
+    else
+    {
+        Assert(AGTE_IS_CONTAINER(entry));
+        result->type = AGTV_BINARY;
+        /* Remove alignment padding from data pointer and length */
+        result->val.binary.data =
+            (agtype_container *)(base_addr + INTALIGN(offset));
+        result->val.binary.len = get_agtype_length(container, index) -
+                                 (INTALIGN(offset) - offset);
+    }
+}
+
+/*
+ * Fast path comparison for scalar agtype containers.
+ *
+ * This function compares two scalar containers directly without the overhead
+ * of the full iterator machinery. It extracts the scalar values using no-copy
+ * fill and compares them directly.
+ *
+ * Returns: negative if a < b, 0 if a == b, positive if a > b
+ */
+static int compare_agtype_scalar_containers(agtype_container *a,
+                                            agtype_container *b)
+{
+    agtype_value va;
+    agtype_value vb;
+    char *base_addr_a;
+    char *base_addr_b;
+    int result;
+    bool need_free_a = false;
+    bool need_free_b = false;
+
+    Assert(AGTYPE_CONTAINER_IS_SCALAR(a));
+    Assert(AGTYPE_CONTAINER_IS_SCALAR(b));
+
+    /* Scalars are stored as single-element arrays */
+    base_addr_a = (char *)&a->children[1];
+    base_addr_b = (char *)&b->children[1];
+
+    /* Use no-copy fill to avoid allocations for simple types */
+    fill_agtype_value_no_copy(a, 0, base_addr_a, 0, &va);
+    fill_agtype_value_no_copy(b, 0, base_addr_b, 0, &vb);
+
+    /*
+     * Check if we need to free the values after comparison.
+     * Only VERTEX, EDGE, and PATH types allocate memory in no-copy mode.
+     */
+    if (va.type == AGTV_VERTEX || va.type == AGTV_EDGE || va.type == AGTV_PATH)
+    {
+        need_free_a = true;
+    }
+    if (vb.type == AGTV_VERTEX || vb.type == AGTV_EDGE || vb.type == AGTV_PATH)
+    {
+        need_free_b = true;
+    }
+
+    /*
+     * Compare the scalar values. If types match or are numeric compatible,
+     * use scalar comparison. Otherwise, use type-based ordering.
+     */
+    if ((va.type == vb.type) ||
+        ((va.type == AGTV_INTEGER || va.type == AGTV_FLOAT ||
+          va.type == AGTV_NUMERIC) &&
+         (vb.type == AGTV_INTEGER || vb.type == AGTV_FLOAT ||
+          vb.type == AGTV_NUMERIC)))
+    {
+        result = compare_agtype_scalar_values(&va, &vb);
+    }
+    else
+    {
+        /* Type-defined order */
+        result = (get_type_sort_priority(va.type) <
+                  get_type_sort_priority(vb.type)) ? -1 : 1;
+    }
+
+    /* Free any allocated memory from composite types */
+    if (need_free_a)
+    {
+        pfree_agtype_value_content(&va);
+    }
+    if (need_free_b)
+    {
+        pfree_agtype_value_content(&vb);
+    }
+
+    return result;
+}
+
 /*
  * Push agtype_value into agtype_parse_state.
  *
@@ -1597,7 +1795,8 @@ void agtype_hash_scalar_value_extended(const agtype_value 
*scalar_val,
     case AGTV_VERTEX:
     {
         graphid id;
-        agtype_value *id_agt = GET_AGTYPE_VALUE_OBJECT_VALUE(scalar_val, "id");
+        agtype_value *id_agt;
+        id_agt = AGTYPE_VERTEX_GET_ID(scalar_val);
         id = id_agt->val.int_value;
         tmp = DatumGetUInt64(DirectFunctionCall2(
             hashint8extended, Float8GetDatum(id), UInt64GetDatum(seed)));
@@ -1606,7 +1805,8 @@ void agtype_hash_scalar_value_extended(const agtype_value 
*scalar_val,
     case AGTV_EDGE:
     {
         graphid id;
-        agtype_value *id_agt = GET_AGTYPE_VALUE_OBJECT_VALUE(scalar_val, "id");
+        agtype_value *id_agt;
+        id_agt = AGTYPE_EDGE_GET_ID(scalar_val);
         id = id_agt->val.int_value;
         tmp = DatumGetUInt64(DirectFunctionCall2(
             hashint8extended, Float8GetDatum(id), UInt64GetDatum(seed)));
@@ -1704,8 +1904,8 @@ static bool equals_agtype_scalar_value(agtype_value *a, 
agtype_value *b)
         case AGTV_VERTEX:
         {
             graphid a_graphid, b_graphid;
-            a_graphid = a->val.object.pairs[0].value.val.int_value;
-            b_graphid = b->val.object.pairs[0].value.val.int_value;
+            a_graphid = AGTYPE_VERTEX_GET_ID(a)->val.int_value;
+            b_graphid = AGTYPE_VERTEX_GET_ID(b)->val.int_value;
 
             return a_graphid == b_graphid;
         }
@@ -1790,16 +1990,33 @@ int compare_agtype_scalar_values(agtype_value *a, 
agtype_value *b)
             return compare_two_floats_orderability(a->val.float_value,
                                                    b->val.float_value);
         case AGTV_VERTEX:
-        case AGTV_EDGE:
         {
-            agtype_value *a_id, *b_id;
             graphid a_graphid, b_graphid;
 
-            a_id = GET_AGTYPE_VALUE_OBJECT_VALUE(a, "id");
-            b_id = GET_AGTYPE_VALUE_OBJECT_VALUE(b, "id");
+            /* Direct field access optimization using macros defined in 
agtype.h. */
+            a_graphid = AGTYPE_VERTEX_GET_ID(a)->val.int_value;
+            b_graphid = AGTYPE_VERTEX_GET_ID(b)->val.int_value;
+
+            if (a_graphid == b_graphid)
+            {
+                return 0;
+            }
+            else if (a_graphid > b_graphid)
+            {
+                return 1;
+            }
+            else
+            {
+                return -1;
+            }
+        }
+        case AGTV_EDGE:
+        {
+            graphid a_graphid, b_graphid;
 
-            a_graphid = a_id->val.int_value;
-            b_graphid = b_id->val.int_value;
+            /* Direct field access optimization using macros defined in 
agtype.h. */
+            a_graphid = AGTYPE_EDGE_GET_ID(a)->val.int_value;
+            b_graphid = AGTYPE_EDGE_GET_ID(b)->val.int_value;
 
             if (a_graphid == b_graphid)
             {
diff --git a/src/include/utils/agtype.h b/src/include/utils/agtype.h
index ab2ba08c..ec912507 100644
--- a/src/include/utils/agtype.h
+++ b/src/include/utils/agtype.h
@@ -322,6 +322,109 @@ enum agtype_value_type
     AGTV_BINARY
 };
 
+/*
+ * Direct field access indices for vertex and edge objects.
+ *
+ * Vertex and edge objects are serialized with keys sorted by length first,
+ * then lexicographically (via uniqueify_agtype_object). This means field
+ * positions are deterministic and can be accessed directly without binary
+ * search, providing O(1) access instead of O(log n).
+ *
+ * Vertex keys by length: "id"(2), "label"(5), "properties"(10)
+ * Edge keys by length: "id"(2), "label"(5), "end_id"(6), "start_id"(8), 
"properties"(10)
+ */
+#define VERTEX_FIELD_ID         0
+#define VERTEX_FIELD_LABEL      1
+#define VERTEX_FIELD_PROPERTIES 2
+#define VERTEX_NUM_FIELDS       3
+
+#define EDGE_FIELD_ID           0
+#define EDGE_FIELD_LABEL        1
+#define EDGE_FIELD_END_ID       2
+#define EDGE_FIELD_START_ID     3
+#define EDGE_FIELD_PROPERTIES   4
+#define EDGE_NUM_FIELDS         5
+
+/*
+ * Macros for direct field access from vertex/edge agtype_value objects.
+ * These avoid the binary search overhead of GET_AGTYPE_VALUE_OBJECT_VALUE.
+ * Validation is integrated - macros will error if field count is incorrect.
+ * Uses GCC statement expressions to allow validation within expressions.
+ */
+#define AGTYPE_VERTEX_GET_ID(v) \
+    ({ \
+        if ((v)->val.object.num_pairs != VERTEX_NUM_FIELDS) \
+            ereport(ERROR, \
+                    (errcode(ERRCODE_DATA_CORRUPTED), \
+                     errmsg("invalid vertex structure: expected %d fields, 
found %d", \
+                            VERTEX_NUM_FIELDS, (v)->val.object.num_pairs))); \
+        &(v)->val.object.pairs[VERTEX_FIELD_ID].value; \
+    })
+#define AGTYPE_VERTEX_GET_LABEL(v) \
+    ({ \
+        if ((v)->val.object.num_pairs != VERTEX_NUM_FIELDS) \
+            ereport(ERROR, \
+                    (errcode(ERRCODE_DATA_CORRUPTED), \
+                     errmsg("invalid vertex structure: expected %d fields, 
found %d", \
+                            VERTEX_NUM_FIELDS, (v)->val.object.num_pairs))); \
+        &(v)->val.object.pairs[VERTEX_FIELD_LABEL].value; \
+    })
+#define AGTYPE_VERTEX_GET_PROPERTIES(v) \
+    ({ \
+        if ((v)->val.object.num_pairs != VERTEX_NUM_FIELDS) \
+            ereport(ERROR, \
+                    (errcode(ERRCODE_DATA_CORRUPTED), \
+                     errmsg("invalid vertex structure: expected %d fields, 
found %d", \
+                            VERTEX_NUM_FIELDS, (v)->val.object.num_pairs))); \
+        &(v)->val.object.pairs[VERTEX_FIELD_PROPERTIES].value; \
+    })
+
+#define AGTYPE_EDGE_GET_ID(e) \
+    ({ \
+        if ((e)->val.object.num_pairs != EDGE_NUM_FIELDS) \
+            ereport(ERROR, \
+                    (errcode(ERRCODE_DATA_CORRUPTED), \
+                     errmsg("invalid edge structure: expected %d fields, found 
%d", \
+                            EDGE_NUM_FIELDS, (e)->val.object.num_pairs))); \
+        &(e)->val.object.pairs[EDGE_FIELD_ID].value; \
+    })
+#define AGTYPE_EDGE_GET_LABEL(e) \
+    ({ \
+        if ((e)->val.object.num_pairs != EDGE_NUM_FIELDS) \
+            ereport(ERROR, \
+                    (errcode(ERRCODE_DATA_CORRUPTED), \
+                     errmsg("invalid edge structure: expected %d fields, found 
%d", \
+                            EDGE_NUM_FIELDS, (e)->val.object.num_pairs))); \
+        &(e)->val.object.pairs[EDGE_FIELD_LABEL].value; \
+    })
+#define AGTYPE_EDGE_GET_END_ID(e) \
+    ({ \
+        if ((e)->val.object.num_pairs != EDGE_NUM_FIELDS) \
+            ereport(ERROR, \
+                    (errcode(ERRCODE_DATA_CORRUPTED), \
+                     errmsg("invalid edge structure: expected %d fields, found 
%d", \
+                            EDGE_NUM_FIELDS, (e)->val.object.num_pairs))); \
+        &(e)->val.object.pairs[EDGE_FIELD_END_ID].value; \
+    })
+#define AGTYPE_EDGE_GET_START_ID(e) \
+    ({ \
+        if ((e)->val.object.num_pairs != EDGE_NUM_FIELDS) \
+            ereport(ERROR, \
+                    (errcode(ERRCODE_DATA_CORRUPTED), \
+                     errmsg("invalid edge structure: expected %d fields, found 
%d", \
+                            EDGE_NUM_FIELDS, (e)->val.object.num_pairs))); \
+        &(e)->val.object.pairs[EDGE_FIELD_START_ID].value; \
+    })
+#define AGTYPE_EDGE_GET_PROPERTIES(e) \
+    ({ \
+        if ((e)->val.object.num_pairs != EDGE_NUM_FIELDS) \
+            ereport(ERROR, \
+                    (errcode(ERRCODE_DATA_CORRUPTED), \
+                     errmsg("invalid edge structure: expected %d fields, found 
%d", \
+                            EDGE_NUM_FIELDS, (e)->val.object.num_pairs))); \
+        &(e)->val.object.pairs[EDGE_FIELD_PROPERTIES].value; \
+    })
+
 /*
  * agtype_value: In-memory representation of agtype.  This is a convenient
  * deserialized representation, that can easily support using the "val"

Reply via email to