Add path extraction(#>, #>>)operators to agtype (#1224)

Original work was done by Josh Innis for issue#282.

The changes included in this PR are as follows:

- Implement the path extraction operators for agtype, similar
  to the one in postgres where this operator works with jsonb operands
- Allow using these operators inside cypher queries
- Added regression tests
diff --git a/age--1.4.0.sql b/age--1.4.0.sql
index d9fbcc5..c9e7345 100644
--- a/age--1.4.0.sql
+++ b/age--1.4.0.sql
@@ -2924,6 +2924,36 @@
   FUNCTION = ag_catalog.agtype_array_element_text
 );
 
+CREATE FUNCTION ag_catalog.agtype_extract_path(agtype, agtype)
+RETURNS agtype
+LANGUAGE c
+IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+-- return the extracted path as agtype
+CREATE OPERATOR #> (
+  LEFTARG = agtype,
+  RIGHTARG = agtype,
+  FUNCTION = ag_catalog.agtype_extract_path
+);
+
+CREATE FUNCTION ag_catalog.agtype_extract_path_text(agtype, agtype)
+RETURNS text
+LANGUAGE c
+IMMUTABLE
+RETURNS NULL ON NULL INPUT
+PARALLEL SAFE
+AS 'MODULE_PATHNAME';
+
+-- return the extracted path as text
+CREATE OPERATOR #>> (
+  LEFTARG = agtype,
+  RIGHTARG = agtype,
+  FUNCTION = ag_catalog.agtype_extract_path_text
+);
+
 --
 -- Contains operators @> <@
 --
diff --git a/regress/expected/jsonb_operators.out b/regress/expected/jsonb_operators.out
index 78b2f89..a665bd6 100644
--- a/regress/expected/jsonb_operators.out
+++ b/regress/expected/jsonb_operators.out
@@ -805,6 +805,504 @@
 DETAIL:  Expected ":", but found "}".
 CONTEXT:  agtype data, line 1: {"null"}
 --
+-- Agtype path extraction operators (#>, #>>)
+--
+/*
+ * #> operator to return the extracted value as agtype
+ */
+SELECT pg_typeof('{"a":"b","c":[1,2,3]}'::agtype #> '["a"]');
+ pg_typeof 
+-----------
+ agtype
+(1 row)
+
+SELECT pg_typeof('[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[-1,"5"]');
+ pg_typeof 
+-----------
+ agtype
+(1 row)
+
+-- left operand is agtype object, right operand should be an array of strings
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '["a"]';
+ ?column? 
+----------
+ "b"
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '["c"]';
+ ?column?  
+-----------
+ [1, 2, 3]
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '[]';
+          ?column?          
+----------------------------
+ {"a": "b", "c": [1, 2, 3]}
+(1 row)
+
+SELECT '{"0": true}'::agtype #> '["0"]';
+ ?column? 
+----------
+ true
+(1 row)
+
+SELECT '{"a":"b","c":{"d": [1,2,3]}}'::agtype #> '["c", "d"]';
+ ?column?  
+-----------
+ [1, 2, 3]
+(1 row)
+
+SELECT '{"a":"b","c":{"d": {"e": -1}}}'::agtype #> '["c", "d", "e"]';
+ ?column? 
+----------
+ -1
+(1 row)
+
+-- left operand is vertex/edge/path, right operand should be an array of strings
+SELECT '{"id": 1125899906842625, "label": "Vertex", "properties": {"a": "xyz", "b": true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::vertex'::agtype #> '[]';
+                                                         ?column?                                                          
+---------------------------------------------------------------------------------------------------------------------------
+ {"a": "xyz", "b": true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}
+(1 row)
+
+SELECT '{"id": 1125899906842625, "label": "Vertex", "properties": {"a": "xyz", "b": true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::vertex'::agtype #> '["e", "h", -2]';
+ ?column? 
+----------
+ []
+(1 row)
+
+SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"a": "xyz", "b" : true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::edge'::agtype #> '[]';
+                                                         ?column?                                                          
+---------------------------------------------------------------------------------------------------------------------------
+ {"a": "xyz", "b": true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}
+(1 row)
+
+SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"a": "xyz", "b" : true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::edge'::agtype #> '["i", "k", "l"]';
+ ?column? 
+----------
+ "mnopq"
+(1 row)
+
+-- left operand is agtype array, right operand should be an array of integers or valid integer strings
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[0]';
+ ?column? 
+----------
+ 0
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[4]';
+   ?column?    
+---------------
+ {"5": "five"}
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[]';
+             ?column?             
+----------------------------------
+ [0, 1, 2, [3, 4], {"5": "five"}]
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[-2]';
+ ?column? 
+----------
+ [3, 4]
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[-2, -1]';
+ ?column? 
+----------
+ 4
+(1 row)
+
+SELECT '[[-3, 1]]'::agtype #> '[0, 1]';
+ ?column? 
+----------
+ 1
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '["0"]';
+ ?column? 
+----------
+ 0
+(1 row)
+
+SELECT '[[-3, 1]]'::agtype #> '["0", "1"]';
+ ?column? 
+----------
+ 1
+(1 row)
+
+SELECT '[[-3, 1]]'::agtype #> '["0", 1]';
+ ?column? 
+----------
+ 1
+(1 row)
+
+SELECT '[[-3, 1]]'::agtype #> '["0", "-1"]';
+ ?column? 
+----------
+ 1
+(1 row)
+
+-- path extraction pattern for arrays nested in object or object nested in array as left operand
+-- having object at top level
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '["c",0]';
+ ?column? 
+----------
+ 1
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '["c",-3]';
+ ?column? 
+----------
+ 1
+(1 row)
+
+SELECT '{"a":["b"],"c":[1,2,3]}'::agtype #> '["a", 0]';
+ ?column? 
+----------
+ "b"
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3]]]}'::agtype #> '["1", 2, 0, 0]';
+ ?column? 
+----------
+ -3
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": true}]]]}'::agtype #> '["1", -1, -1, -1, "a"]';
+ ?column? 
+----------
+ true
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a"]';
+                     ?column?                     
+--------------------------------------------------
+ {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a", "c"]';
+ ?column? 
+----------
+ "foo"
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a", "b", "d", -2]';
+   ?column?    
+---------------
+ -1.9::numeric
+(1 row)
+
+-- having array at top level
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[4,"5"]';
+ ?column? 
+----------
+ "five"
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[-1,"5"]';
+ ?column? 
+----------
+ "five"
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":["five", "six"]}]'::agtype #> '[-1,"5",-1]';
+ ?column? 
+----------
+ "six"
+(1 row)
+
+-- should return null
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '[0]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '["c",3]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '["c","3"]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3,4]}'::agtype #> '["c",4]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '["c",-4]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '["a","b"]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '["a","c"]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '["a", []]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"a":["b"],"c":[1,2,3]}'::agtype #> '["a", []]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a", "c", "d"]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a", "b", "d", -2, 0]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"0": true}'::agtype #> '[0]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '[null]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{}'::agtype #> '[null]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{}'::agtype #> '[{}]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"id": 1125899906842625, "label": "Vertex", "properties": {"a": "xyz", "b": true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::vertex'::agtype #> '["id"]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"a": "xyz", "b" : true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::edge'::agtype #> '["start_id"]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[4,5]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[-1,5]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[3, -1, 0]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[{}]'::agtype;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[{"5":"five"}]'::agtype;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"},6,7]'::agtype #> '["6", "7"]'::agtype;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"},6,7]'::agtype #> '[6, 7]'::agtype;
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[null]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '[null]'::agtype #> '[null]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '[]'::agtype #> '[null]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '[[-3, 1]]'::agtype #> '["0", "1.1"]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '[[-3, 1]]'::agtype #> '["0", "true"]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '[[-3, 1]]'::agtype #> '["0", "string"]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a", "b", "d", "false"]';
+ ?column? 
+----------
+ 
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a", "b", "d", "a"]';
+ ?column? 
+----------
+ 
+(1 row)
+
+-- errors out
+SELECT '{"0": true}'::agtype #> '"0"';
+ERROR:  right operand must be an array
+SELECT '{"n": 1}'::agtype #> '{"n": 1}';
+ERROR:  right operand must be an array
+SELECT '[{"n": 1}]'::agtype #> '{"n": 1}';
+ERROR:  right operand must be an array
+SELECT '[{"n": 100}]'::agtype #> '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex';
+ERROR:  right operand must be an array
+SELECT '-19'::agtype #> '[-1]'::agtype;
+ERROR:  scalar object must be a vertex or edge
+SELECT '{"id": 1125899906842625, "label": "Vertex", "properties": {"a": "xyz", "b": true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::vertex'::agtype #> '"a"';
+ERROR:  right operand must be an array
+/*
+ * #>> operator to return the extracted value as text
+ */
+SELECT pg_typeof('{"a":"b","c":[1,2,3]}'::agtype #>> '["a"]');
+ pg_typeof 
+-----------
+ text
+(1 row)
+
+SELECT pg_typeof('[0,1,2,[3,4],{"5":"five"}]'::agtype #>> '[-1,"5"]');
+ pg_typeof 
+-----------
+ text
+(1 row)
+
+/*
+ * All the tests added for #> are also valid for #>>
+ */
+/*
+ * test the combination of #> and #>> operators below
+ * (left and right operands have to be agtype for #> and #>>,
+ * errors out when left operand is a text, i.e., the output of #>> operator)
+ */
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a"]' #> '["b", "d", -1]';
+ ?column? 
+----------
+ false
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a"]' #> '["b", "d", "-1"]';
+ ?column? 
+----------
+ false
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a"]' #>> '["b", "d", -1]';
+ ?column? 
+----------
+ false
+(1 row)
+
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a"]' #>> '["b", "d", "-1"]';
+ ?column? 
+----------
+ false
+(1 row)
+
+SELECT '{"id": 1125899906842625, "label": "Vertex", "properties": {"a": "xyz", "b": true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::vertex'::agtype #> '["e"]' #>> '["h", "-1"]';
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{"id": 1125899906842625, "label": "Vertex", "properties": {"a": "xyz", "b": true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::vertex'::agtype #> '["e"]' #> '["h", "-1"]' #>> '[]';
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '[[-3, [1]]]'::agtype #> '["0", "1"]' #>> '[]';
+ ?column? 
+----------
+ [1]
+(1 row)
+
+SELECT '[[-3, [1]]]'::agtype #> '["0", "1"]' #>> '[-1]';
+ ?column? 
+----------
+ 1
+(1 row)
+
+-- errors out
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #>> '[-1, "5"]' #> '[]';
+ERROR:  operator does not exist: text #> unknown
+LINE 1: ...[0,1,2,[3,4],{"5":"five"}]'::agtype #>> '[-1, "5"]' #> '[]';
+                                                               ^
+HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #>> '["1", -1, -1, -1, "a"]' #> '["b", "d", -1]';
+ERROR:  operator does not exist: text #> unknown
+LINE 1: ..."foo"}}]]]}'::agtype #>> '["1", -1, -1, -1, "a"]' #> '["b", ...
+                                                             ^
+HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+--
 -- concat || operator
 --
 SELECT i, pg_typeof(i) FROM (SELECT '[0, 1]'::agtype || '[0, 1]'::agtype as i) a;
@@ -1548,6 +2046,260 @@
 SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& n.json $$) as (a agtype);
 ERROR:  invalid agtype value for right operand
 --
+-- path extraction #> operator
+--
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH {json: {a: 1, b: ['a', 'b'], c: {d: ['a']}}, list: ['a', 'b', 'c']} AS map
+    RETURN map #> []
+$$) AS (result agtype);
+                                     result                                      
+---------------------------------------------------------------------------------
+ {"json": {"a": 1, "b": ["a", "b"], "c": {"d": ["a"]}}, "list": ["a", "b", "c"]}
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH {json: {a: 1, b: ['a', 'b'], c: {d: ['a']}}, list: ['a', 'b', 'c']} AS map
+    RETURN map #> ['json', 'c', 'd']
+$$) AS (result agtype);
+ result 
+--------
+ ["a"]
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH {json: {a: 1, b: ['a', 'b'], c: {d: ['a']}}, list: ['a', 'b', 'c']} AS map
+    RETURN map #> ['json', 'c', 'd', -1]
+$$) AS (result agtype);
+ result 
+--------
+ "a"
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH {json: {a: 1, b: ['a', 'b'], c: {d: ['a']}}, list: ['a', 'b', 'c']} AS map
+    RETURN map #> ['json', 'c', 'd', -1, -1]
+$$) AS (result agtype);
+ result 
+--------
+ 
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH {json: {a: 1, b: ['a', 'b'], c: {d: ['a']}}, list: ['a', 'b', 'c']} AS map
+    RETURN map #> ['list', "-1"]
+$$) AS (result agtype);
+ result 
+--------
+ "c"
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH {json: {a: 1, b: ['a', 'b'], c: {d: ['a']}}, list: ['a', 'b', ['c', 'd']]} AS map
+    RETURN map #> ['list', "-1", "-1"]
+$$) AS (result agtype);
+ result 
+--------
+ "d"
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH {json: {a: 1, b: ['a', 'b'], c: {d: ['a']}}, list: ['a', 'b', ['c', 'd']]} AS map
+    RETURN map #> ['list', "-1", -1]
+$$) AS (result agtype);
+ result 
+--------
+ "d"
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH [[-3, 1]] AS list
+    RETURN list #> []
+$$) AS (result agtype);
+  result   
+-----------
+ [[-3, 1]]
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH [[-3, 1]] AS list
+    RETURN list #> [0]
+$$) AS (result agtype);
+ result  
+---------
+ [-3, 1]
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH [[-3, 1]] AS list
+    RETURN list #> [-1, -1]
+$$) AS (result agtype);
+ result 
+--------
+ 1
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH [[-3, 1]] AS list
+    RETURN list #> [-1, -1, -1]
+$$) AS (result agtype);
+ result 
+--------
+ 
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH [[-3, 1]] AS list
+    RETURN list #> [{}]
+$$) AS (result agtype);
+ result 
+--------
+ 
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH [null] AS list
+    RETURN list #> []
+$$) AS (result agtype);
+ result 
+--------
+ [null]
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH [null] AS list
+    RETURN list #> [-1, -1, -1]
+$$) AS (result agtype);
+ result 
+--------
+ 
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH [] AS list
+    RETURN list #> []
+$$) AS (result agtype);
+ result 
+--------
+ []
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH [] AS list
+    RETURN list #> ["a", 1]
+$$) AS (result agtype);
+ result 
+--------
+ 
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> []$$) as (a agtype);
+                                       a                                       
+-------------------------------------------------------------------------------
+ {"json": {"a": 1, "b": ["a", "b"], "c": {"d": "a"}}, "list": ["a", "b", "c"]}
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["json"]$$) as (a agtype);
+                     a                      
+--------------------------------------------
+ {"a": 1, "b": ["a", "b"], "c": {"d": "a"}}
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["jsonb"]$$) as (a agtype);
+ a 
+---
+ 
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["json", "a"]$$) as (a agtype);
+ a 
+---
+ 1
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["json", "a", 0]$$) as (a agtype);
+ a 
+---
+ 
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["json", "b"]$$) as (a agtype);
+     a      
+------------
+ ["a", "b"]
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["json", "b", -1]$$) as (a agtype);
+  a  
+-----
+ "b"
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["json", "b", "-1"]$$) as (a agtype);
+  a  
+-----
+ "b"
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["json", "b", -1, 0]$$) as (a agtype);
+ a 
+---
+ 
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["json", "c"]$$) as (a agtype);
+     a      
+------------
+ {"d": "a"}
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["json", "c", "d"]$$) as (a agtype);
+  a  
+-----
+ "a"
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["json", "c", "d", -1]$$) as (a agtype);
+ a 
+---
+ 
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ['list', -1]$$) as (a agtype);
+  a  
+-----
+ "c"
+(1 row)
+
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ['list', 4]$$) as (a agtype);
+ a 
+---
+ 
+(1 row)
+
+-- errors out
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> "json"$$) as (a agtype);
+ERROR:  right operand must be an array
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH {json: {a: 1, b: ['a', 'b'], c: {d: ['a']}}, list: ['a', 'b', 'c']} AS map
+    RETURN map #> 'jsonb'
+$$) AS (result agtype);
+ERROR:  right operand must be an array
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH [[-3, 1]] AS list
+    RETURN list #> 0
+$$) AS (result agtype);
+ERROR:  right operand must be an array
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH 3 AS elem
+    RETURN elem #> [0]
+$$) AS (result agtype);
+ERROR:  scalar object must be a vertex or edge
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH 'string' AS elem
+    RETURN elem #> [0]
+$$) AS (result agtype);
+ERROR:  scalar object must be a vertex or edge
+--
 -- concat || operator
 --
 SELECT * FROM cypher('jsonb_operators', $$ RETURN [1,2] || 2 $$) AS (result agtype);
diff --git a/regress/sql/jsonb_operators.sql b/regress/sql/jsonb_operators.sql
index 689c148..e8ed488 100644
--- a/regress/sql/jsonb_operators.sql
+++ b/regress/sql/jsonb_operators.sql
@@ -189,6 +189,132 @@
 SELECT '{"a":null, "b":"qq"}'::agtype ?& '{"null"}';
 
 --
+-- Agtype path extraction operators (#>, #>>)
+--
+
+/*
+ * #> operator to return the extracted value as agtype
+ */
+SELECT pg_typeof('{"a":"b","c":[1,2,3]}'::agtype #> '["a"]');
+SELECT pg_typeof('[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[-1,"5"]');
+
+-- left operand is agtype object, right operand should be an array of strings
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '["a"]';
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '["c"]';
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '[]';
+SELECT '{"0": true}'::agtype #> '["0"]';
+SELECT '{"a":"b","c":{"d": [1,2,3]}}'::agtype #> '["c", "d"]';
+SELECT '{"a":"b","c":{"d": {"e": -1}}}'::agtype #> '["c", "d", "e"]';
+
+-- left operand is vertex/edge/path, right operand should be an array of strings
+SELECT '{"id": 1125899906842625, "label": "Vertex", "properties": {"a": "xyz", "b": true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::vertex'::agtype #> '[]';
+SELECT '{"id": 1125899906842625, "label": "Vertex", "properties": {"a": "xyz", "b": true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::vertex'::agtype #> '["e", "h", -2]';
+SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"a": "xyz", "b" : true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::edge'::agtype #> '[]';
+SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"a": "xyz", "b" : true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::edge'::agtype #> '["i", "k", "l"]';
+
+-- left operand is agtype array, right operand should be an array of integers or valid integer strings
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[0]';
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[4]';
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[]';
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[-2]';
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[-2, -1]';
+SELECT '[[-3, 1]]'::agtype #> '[0, 1]';
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '["0"]';
+SELECT '[[-3, 1]]'::agtype #> '["0", "1"]';
+SELECT '[[-3, 1]]'::agtype #> '["0", 1]';
+SELECT '[[-3, 1]]'::agtype #> '["0", "-1"]';
+
+-- path extraction pattern for arrays nested in object or object nested in array as left operand
+-- having object at top level
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '["c",0]';
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '["c",-3]';
+SELECT '{"a":["b"],"c":[1,2,3]}'::agtype #> '["a", 0]';
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3]]]}'::agtype #> '["1", 2, 0, 0]';
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": true}]]]}'::agtype #> '["1", -1, -1, -1, "a"]';
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a"]';
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a", "c"]';
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a", "b", "d", -2]';
+
+-- having array at top level
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[4,"5"]';
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[-1,"5"]';
+SELECT '[0,1,2,[3,4],{"5":["five", "six"]}]'::agtype #> '[-1,"5",-1]';
+
+-- should return null
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '[0]';
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '["c",3]';
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '["c","3"]';
+SELECT '{"a":"b","c":[1,2,3,4]}'::agtype #> '["c",4]';
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '["c",-4]';
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '["a","b"]';
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '["a","c"]';
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '["a", []]';
+SELECT '{"a":["b"],"c":[1,2,3]}'::agtype #> '["a", []]';
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a", "c", "d"]';
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a", "b", "d", -2, 0]';
+SELECT '{"0": true}'::agtype #> '[0]';
+SELECT '{"a":"b","c":[1,2,3]}'::agtype #> '[null]';
+SELECT '{}'::agtype #> '[null]';
+SELECT '{}'::agtype #> '[{}]';
+
+SELECT '{"id": 1125899906842625, "label": "Vertex", "properties": {"a": "xyz", "b": true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::vertex'::agtype #> '["id"]';
+SELECT '{"id": 1688849860263937, "label": "EDGE", "end_id": 1970324836974593, "start_id": 1407374883553281, "properties": {"a": "xyz", "b" : true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::edge'::agtype #> '["start_id"]';
+
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[4,5]';
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[-1,5]';
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[3, -1, 0]';
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[{}]'::agtype;
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[{"5":"five"}]'::agtype;
+SELECT '[0,1,2,[3,4],{"5":"five"},6,7]'::agtype #> '["6", "7"]'::agtype;
+SELECT '[0,1,2,[3,4],{"5":"five"},6,7]'::agtype #> '[6, 7]'::agtype;
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #> '[null]';
+SELECT '[null]'::agtype #> '[null]';
+SELECT '[]'::agtype #> '[null]';
+SELECT '[[-3, 1]]'::agtype #> '["0", "1.1"]';
+SELECT '[[-3, 1]]'::agtype #> '["0", "true"]';
+SELECT '[[-3, 1]]'::agtype #> '["0", "string"]';
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a", "b", "d", "false"]';
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a", "b", "d", "a"]';
+
+-- errors out
+SELECT '{"0": true}'::agtype #> '"0"';
+SELECT '{"n": 1}'::agtype #> '{"n": 1}';
+SELECT '[{"n": 1}]'::agtype #> '{"n": 1}';
+SELECT '[{"n": 100}]'::agtype #> '{"id": 281474976710658, "label": "", "properties": {"n": 100}}::vertex';
+SELECT '-19'::agtype #> '[-1]'::agtype;
+SELECT '{"id": 1125899906842625, "label": "Vertex", "properties": {"a": "xyz", "b": true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::vertex'::agtype #> '"a"';
+
+/*
+ * #>> operator to return the extracted value as text
+ */
+SELECT pg_typeof('{"a":"b","c":[1,2,3]}'::agtype #>> '["a"]');
+SELECT pg_typeof('[0,1,2,[3,4],{"5":"five"}]'::agtype #>> '[-1,"5"]');
+
+/*
+ * All the tests added for #> are also valid for #>>
+ */
+
+/*
+ * test the combination of #> and #>> operators below
+ * (left and right operands have to be agtype for #> and #>>,
+ * errors out when left operand is a text, i.e., the output of #>> operator)
+ */
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a"]' #> '["b", "d", -1]';
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a"]' #> '["b", "d", "-1"]';
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a"]' #>> '["b", "d", -1]';
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #> '["1", -1, -1, -1, "a"]' #>> '["b", "d", "-1"]';
+
+SELECT '{"id": 1125899906842625, "label": "Vertex", "properties": {"a": "xyz", "b": true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::vertex'::agtype #> '["e"]' #>> '["h", "-1"]';
+SELECT '{"id": 1125899906842625, "label": "Vertex", "properties": {"a": "xyz", "b": true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::vertex'::agtype #> '["e"]' #> '["h", "-1"]' #>> '[]';
+
+SELECT '[[-3, [1]]]'::agtype #> '["0", "1"]' #>> '[]';
+SELECT '[[-3, [1]]]'::agtype #> '["0", "1"]' #>> '[-1]';
+
+-- errors out
+SELECT '[0,1,2,[3,4],{"5":"five"}]'::agtype #>> '[-1, "5"]' #> '[]';
+SELECT '{"a":"b","c":[1,2,3], "1" : [{}, {}, [[-3, {"a": {"b": {"d": [-1.9::numeric, false]}, "c": "foo"}}]]]}'::agtype #>> '["1", -1, -1, -1, "a"]' #> '["b", "d", -1]';
+
+--
 -- concat || operator
 --
 SELECT i, pg_typeof(i) FROM (SELECT '[0, 1]'::agtype || '[0, 1]'::agtype as i) a;
@@ -378,6 +504,123 @@
 SELECT * FROM cypher('jsonb_operators',$$MATCH (n) return n ?& n.json $$) as (a agtype);
 
 --
+-- path extraction #> operator
+--
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH {json: {a: 1, b: ['a', 'b'], c: {d: ['a']}}, list: ['a', 'b', 'c']} AS map
+    RETURN map #> []
+$$) AS (result agtype);
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH {json: {a: 1, b: ['a', 'b'], c: {d: ['a']}}, list: ['a', 'b', 'c']} AS map
+    RETURN map #> ['json', 'c', 'd']
+$$) AS (result agtype);
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH {json: {a: 1, b: ['a', 'b'], c: {d: ['a']}}, list: ['a', 'b', 'c']} AS map
+    RETURN map #> ['json', 'c', 'd', -1]
+$$) AS (result agtype);
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH {json: {a: 1, b: ['a', 'b'], c: {d: ['a']}}, list: ['a', 'b', 'c']} AS map
+    RETURN map #> ['json', 'c', 'd', -1, -1]
+$$) AS (result agtype);
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH {json: {a: 1, b: ['a', 'b'], c: {d: ['a']}}, list: ['a', 'b', 'c']} AS map
+    RETURN map #> ['list', "-1"]
+$$) AS (result agtype);
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH {json: {a: 1, b: ['a', 'b'], c: {d: ['a']}}, list: ['a', 'b', ['c', 'd']]} AS map
+    RETURN map #> ['list', "-1", "-1"]
+$$) AS (result agtype);
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH {json: {a: 1, b: ['a', 'b'], c: {d: ['a']}}, list: ['a', 'b', ['c', 'd']]} AS map
+    RETURN map #> ['list', "-1", -1]
+$$) AS (result agtype);
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH [[-3, 1]] AS list
+    RETURN list #> []
+$$) AS (result agtype);
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH [[-3, 1]] AS list
+    RETURN list #> [0]
+$$) AS (result agtype);
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH [[-3, 1]] AS list
+    RETURN list #> [-1, -1]
+$$) AS (result agtype);
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH [[-3, 1]] AS list
+    RETURN list #> [-1, -1, -1]
+$$) AS (result agtype);
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH [[-3, 1]] AS list
+    RETURN list #> [{}]
+$$) AS (result agtype);
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH [null] AS list
+    RETURN list #> []
+$$) AS (result agtype);
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH [null] AS list
+    RETURN list #> [-1, -1, -1]
+$$) AS (result agtype);
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH [] AS list
+    RETURN list #> []
+$$) AS (result agtype);
+
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH [] AS list
+    RETURN list #> ["a", 1]
+$$) AS (result agtype);
+
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> []$$) as (a agtype);
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["json"]$$) as (a agtype);
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["jsonb"]$$) as (a agtype);
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["json", "a"]$$) as (a agtype);
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["json", "a", 0]$$) as (a agtype);
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["json", "b"]$$) as (a agtype);
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["json", "b", -1]$$) as (a agtype);
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["json", "b", "-1"]$$) as (a agtype);
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["json", "b", -1, 0]$$) as (a agtype);
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["json", "c"]$$) as (a agtype);
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["json", "c", "d"]$$) as (a agtype);
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ["json", "c", "d", -1]$$) as (a agtype);
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ['list', -1]$$) as (a agtype);
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> ['list', 4]$$) as (a agtype);
+
+-- errors out
+SELECT * FROM cypher('jsonb_operators',$$MATCH (n) RETURN n #> "json"$$) as (a agtype);
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH {json: {a: 1, b: ['a', 'b'], c: {d: ['a']}}, list: ['a', 'b', 'c']} AS map
+    RETURN map #> 'jsonb'
+$$) AS (result agtype);
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH [[-3, 1]] AS list
+    RETURN list #> 0
+$$) AS (result agtype);
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH 3 AS elem
+    RETURN elem #> [0]
+$$) AS (result agtype);
+SELECT * FROM cypher('jsonb_operators', $$
+    WITH 'string' AS elem
+    RETURN elem #> [0]
+$$) AS (result agtype);
+
+--
 -- concat || operator
 --
 SELECT * FROM cypher('jsonb_operators', $$ RETURN [1,2] || 2 $$) AS (result agtype);
diff --git a/src/backend/parser/ag_scanner.l b/src/backend/parser/ag_scanner.l
index 74887ba..aee021b 100644
--- a/src/backend/parser/ag_scanner.l
+++ b/src/backend/parser/ag_scanner.l
@@ -227,17 +227,18 @@
  * These are tokens that are used as operators and language constructs in
  * Cypher, and some of them are structural characters in JSON.
  */
-any_exists "?|"
-all_exists "?&"
-concat     "||"
-lt_gt      "<>"
-lt_eq      "<="
-gt_eq      ">="
-dot_dot    ".."
-plus_eq    "+="
-eq_tilde   "=~"
-typecast   "::"
-self       [?%()*+,\-./:;<=>[\]^{|}]
+any_exists  "?|"
+all_exists  "?&"
+concat      "||"
+access_path "#>"
+lt_gt       "<>"
+lt_eq       "<="
+gt_eq       ">="
+dot_dot     ".."
+plus_eq     "+="
+eq_tilde    "=~"
+typecast    "::"
+self        [?%()*+,\-./:;<=>[\]^{|}]
 
 other .
 
@@ -653,6 +654,14 @@
     return token;
 }
 
+{access_path} {
+    update_location();
+    token.type = AG_TOKEN_ACCESS_PATH;
+    token.value.s = yytext;
+    token.location = get_location();
+    return token;
+}
+
 {any_exists} {
     update_location();
     token.type = AG_TOKEN_ANY_EXISTS;
diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y
index 884af45..ff06786 100644
--- a/src/backend/parser/cypher_gram.y
+++ b/src/backend/parser/cypher_gram.y
@@ -76,7 +76,7 @@
 
 /* operators that have more than 1 character */
 %token NOT_EQ LT_EQ GT_EQ DOT_DOT TYPECAST PLUS_EQ EQ_TILDE CONCAT
-%token ANY_EXISTS ALL_EXISTS
+%token ACCESS_PATH ANY_EXISTS ALL_EXISTS
 
 /* keywords in alphabetical order */
 %token <keyword> ALL ANALYZE AND AS ASC ASCENDING
@@ -178,7 +178,7 @@
 %right UNARY_MINUS
 %nonassoc CONTAINS ENDS EQ_TILDE STARTS
 %left '[' ']' '(' ')'
-%left '.'
+%left '.' ACCESS_PATH
 %left TYPECAST
 
 /*set operations*/
@@ -1342,6 +1342,10 @@
         {
             $$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "||", $1, $3, @2);
         }
+    | expr ACCESS_PATH expr
+        {
+	        $$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "#>", $1, $3, @2);
+        }
     | expr '+' expr
         {
             $$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "+", $1, $3, @2);
diff --git a/src/backend/parser/cypher_parser.c b/src/backend/parser/cypher_parser.c
index f9dfae7..dcd499d 100644
--- a/src/backend/parser/cypher_parser.c
+++ b/src/backend/parser/cypher_parser.c
@@ -47,6 +47,7 @@
         TYPECAST,
         PLUS_EQ,
         EQ_TILDE,
+        ACCESS_PATH,
         ANY_EXISTS,
         ALL_EXISTS,
         CONCAT
@@ -101,6 +102,7 @@
     case AG_TOKEN_DOT_DOT:
     case AG_TOKEN_PLUS_EQ:
     case AG_TOKEN_EQ_TILDE:
+    case AG_TOKEN_ACCESS_PATH:
     case AG_TOKEN_ALL_EXISTS:
     case AG_TOKEN_ANY_EXISTS:
     case AG_TOKEN_CONCAT:
diff --git a/src/backend/utils/adt/agtype_ops.c b/src/backend/utils/adt/agtype_ops.c
index 916f4df..b1e8f66 100644
--- a/src/backend/utils/adt/agtype_ops.c
+++ b/src/backend/utils/adt/agtype_ops.c
@@ -24,6 +24,7 @@
 #include "postgres.h"
 
 #include <math.h>
+#include <limits.h>
 
 #include "catalog/pg_type_d.h"
 #include "fmgr.h"
@@ -40,6 +41,7 @@
 static void concat_to_agtype_string(agtype_value *result, char *lhs, int llen,
                                     char *rhs, int rlen);
 static char *get_string_from_agtype_value(agtype_value *agtv, int *length);
+static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text);
 
 static void concat_to_agtype_string(agtype_value *result, char *lhs, int llen,
                                     char *rhs, int rlen)
@@ -1580,6 +1582,237 @@
     return res;
 }
 
+/*
+ * agtype path extraction operator '#>'. The right operand can
+ * either be an array of object keys or array indexes for extracting
+ * agtype sub-object or sub-array from the left operand.
+ */
+PG_FUNCTION_INFO_V1(agtype_extract_path);
+
+Datum agtype_extract_path(PG_FUNCTION_ARGS)
+{
+    return get_agtype_path_all(fcinfo, false);
+}
+
+/*
+ * agtype path extraction operator '#>>' that returns the extracted path
+ * as text.
+ */
+PG_FUNCTION_INFO_V1(agtype_extract_path_text);
+
+Datum agtype_extract_path_text(PG_FUNCTION_ARGS)
+{
+    return get_agtype_path_all(fcinfo, true);
+}
+
+static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text)
+{
+    agtype *agt = AG_GET_ARG_AGTYPE_P(0);
+    agtype *path = AG_GET_ARG_AGTYPE_P(1);
+    agtype *res;
+    int npath;
+    int i;
+    bool have_object = false, have_array = false;
+    agtype_value *agtvp = NULL;
+    agtype_value tv;
+    agtype_container *container;
+
+    if (AGT_ROOT_IS_SCALAR(path) || AGT_ROOT_IS_OBJECT(path))
+    {
+        ereport(ERROR,(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                       errmsg("right operand must be an array")));
+    }
+
+    if (AGT_ROOT_IS_SCALAR(agt))
+    {
+        agt = agtype_value_to_agtype(extract_entity_properties(agt, true));
+    }
+
+    npath = AGT_ROOT_COUNT(path);
+    container = &agt->root;
+
+    /* Identify whether we have object, array, or scalar at top-level */
+    if (AGT_ROOT_IS_OBJECT(agt))
+    {
+        have_object = true;
+    }
+    else if (AGT_ROOT_IS_ARRAY(agt) && !AGT_ROOT_IS_SCALAR(agt))
+    {
+        have_array = true;
+    }
+    else
+    {
+        Assert(AGT_ROOT_IS_ARRAY(agt) && AGT_ROOT_IS_SCALAR(agt));
+
+        /* Extract the scalar value */
+        if (npath <= 0)
+        {
+            agtvp = get_ith_agtype_value_from_container(container, 0);
+        }
+    }
+
+    /*
+     * If RHS array is empty, return the entire LHS object/array, based on the
+     * assumption that we should not do any field or element extractions. In
+     * case of non-scalar, we can just hand back the agtype without much
+     * work but for the scalar case, fall through and deal with the value
+     * below the loop (This inconsistency arises because there's no easy way to
+     * generate an agtype_value directly for root-level containers)
+     */
+    if (npath <= 0 && agtvp == NULL)
+    {
+        if (as_text)
+        {
+            PG_RETURN_TEXT_P(cstring_to_text(agtype_to_cstring(NULL, container,
+                                                               VARSIZE(agt))));
+        }
+        else
+        {
+            /* not text mode - just hand back the agtype */
+            AG_RETURN_AGTYPE_P(agt);
+        }
+    }
+
+    for (i = 0; i < npath; i++)
+    {
+        agtype_value *cur_key =
+            get_ith_agtype_value_from_container(&path->root, i);
+
+        if (have_object && cur_key->type == AGTV_STRING)
+        {
+            agtvp = find_agtype_value_from_container(container,
+                                                     AGT_FOBJECT,
+                                                     cur_key);
+        }
+        else if (have_array)
+        {
+            long lindex;
+            uint32 index;
+
+            /*
+             * for array on LHS, there should be an integer or a
+             * valid integer string on RHS
+             */
+            if (cur_key->type == AGTV_INTEGER)
+            {
+                lindex = cur_key->val.int_value;
+            }
+            else if (cur_key->type == AGTV_STRING)
+            {
+                /*
+                 * extract the integer from the string,
+                 * if character other than a digit is found, return null
+                 */
+                char* str = NULL;
+                lindex = strtol(cur_key->val.string.val, &str, 10);
+
+                if (strcmp(str, ""))
+                {
+                    PG_RETURN_NULL();
+                }
+            }
+            else
+            {
+                PG_RETURN_NULL();
+            }
+
+            if (lindex > INT_MAX || lindex < INT_MIN)
+            {
+                PG_RETURN_NULL();
+            }
+
+            if (lindex >= 0)
+            {
+                index = (uint32) lindex;
+            }
+            else
+            {
+                /* Handle negative subscript */
+                uint32 nelements;
+
+                /* Container must be an array, but make sure */
+                if (!AGTYPE_CONTAINER_IS_ARRAY(container))
+                {
+                    elog(ERROR, "not an agtype array");
+                }
+
+                nelements = AGTYPE_CONTAINER_SIZE(container);
+
+                if (-lindex > nelements)
+                {
+                    PG_RETURN_NULL();
+                }
+                else
+                {
+                    index = nelements + lindex;
+                }
+            }
+
+            agtvp = get_ith_agtype_value_from_container(container, index);
+        }
+        else
+        {
+            PG_RETURN_NULL();
+        }
+
+        if (agtvp == NULL)
+        {
+            PG_RETURN_NULL();
+        }
+        else if (i == npath - 1)
+        {
+            break;
+        }
+
+        if (agtvp->type == AGTV_BINARY)
+        {
+            agtype_iterator_token r;
+            agtype_iterator *it =
+                agtype_iterator_init((agtype_container *)
+                                      agtvp->val.binary.data);
+
+            r = agtype_iterator_next(&it, &tv, true);
+            container = (agtype_container *) agtvp->val.binary.data;
+            have_object = r == WAGT_BEGIN_OBJECT;
+            have_array = r == WAGT_BEGIN_ARRAY;
+        }
+        else
+        {
+            have_object = agtvp->type == AGTV_OBJECT;
+            have_array = agtvp->type == AGTV_ARRAY;
+        }
+    }
+
+    if (as_text)
+    {
+        /* special-case output for string and null values */
+        if (agtvp->type == AGTV_STRING)
+        {
+            PG_RETURN_TEXT_P(cstring_to_text_with_len(agtvp->val.string.val,
+                                                      agtvp->val.string.len));
+        }
+
+        if (agtvp->type == AGTV_NULL)
+        {
+            PG_RETURN_NULL();
+        }
+    }
+
+    res = agtype_value_to_agtype(agtvp);
+
+    if (as_text)
+    {
+        PG_RETURN_TEXT_P(cstring_to_text(agtype_to_cstring(NULL,
+                                                           &res->root,
+                                                           VARSIZE(res))));
+    }
+    else
+    {
+        /* not text mode - just hand back the agtype */
+        AG_RETURN_AGTYPE_P(res);
+    }
+}
+
 static void ereport_op_str(const char *op, agtype *lhs, agtype *rhs)
 {
     const char *msgfmt;
diff --git a/src/include/parser/ag_scanner.h b/src/include/parser/ag_scanner.h
index cb11b44..edf71b4 100644
--- a/src/include/parser/ag_scanner.h
+++ b/src/include/parser/ag_scanner.h
@@ -46,6 +46,7 @@
     AG_TOKEN_TYPECAST,
     AG_TOKEN_PLUS_EQ,
     AG_TOKEN_EQ_TILDE,
+    AG_TOKEN_ACCESS_PATH,
     AG_TOKEN_ANY_EXISTS,
     AG_TOKEN_ALL_EXISTS,
     AG_TOKEN_CONCAT,