Skip to content

Commit

Permalink
Add support for external extensions (#2088)
Browse files Browse the repository at this point in the history
- This commit allows the functions from external extensions to be
  called from the Cypher queries, provided that typecast is available
  for args and return type of that function. The extension should be
  installed and the function should be in the search path.

- Added cypher typecast for pgvector datatypes, its not a direct cast.
  It casts agtype to text and then text to vector.

- Added regression tests for pg_trgm, fuzzystrmatch and pgvector
  extensions. pg_trgm is another extension that is used for fuzzy
  string matching. These regression test are extra tests that need to
  be explicitly added to the regression suite.
  Following command can be used to do so:
  make installcheck EXTRA_TESTS="pg_trgm pgvector fuzzystrmatch"

- Updated CI to run the extra tests for the extensions.
  • Loading branch information
MuhammadTahaNaveed authored Sep 11, 2024
1 parent bd7dd4c commit 3247728
Show file tree
Hide file tree
Showing 11 changed files with 1,191 additions and 272 deletions.
18 changes: 15 additions & 3 deletions .github/workflows/installcheck.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,37 @@ jobs:
path: ~/pg16
key: ${{ runner.os }}-v1-pg16-${{ env.PG_COMMIT_HASH }}

- name: Install PostgreSQL 16
- name: Install PostgreSQL 16 and some extensions
if: steps.pg16cache.outputs.cache-hit != 'true'
run: |
git clone --depth 1 --branch REL_16_STABLE git://git.postgresql.org/git/postgresql.git ~/pg16source
cd ~/pg16source
./configure --prefix=$HOME/pg16 CFLAGS="-std=gnu99 -ggdb -O0" --enable-cassert
make install -j$(nproc) > /dev/null
cd contrib
cd fuzzystrmatch
make PG_CONFIG=$HOME/pg16/bin/pg_config install -j$(nproc) > /dev/null
cd ../pg_trgm
make PG_CONFIG=$HOME/pg16/bin/pg_config install -j$(nproc) > /dev/null
- uses: actions/checkout@v3

- name: Build
- name: Build AGE
id: build
run: |
make PG_CONFIG=$HOME/pg16/bin/pg_config install -j$(nproc)
- name: Pull and build pgvector
id: pgvector
run: |
git clone https://github.com/pgvector/pgvector.git
cd pgvector
make PG_CONFIG=$HOME/pg16/bin/pg_config install -j$(nproc) > /dev/null
- name: Regression tests
id: regression_tests
run: |
make PG_CONFIG=$HOME/pg16/bin/pg_config installcheck
make PG_CONFIG=$HOME/pg16/bin/pg_config installcheck EXTRA_TESTS="pgvector fuzzystrmatch pg_trgm"
continue-on-error: true

- name: Dump regression test errors
Expand Down
9 changes: 7 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,13 @@ REGRESS = scan \
name_validation \
jsonb_operators \
list_comprehension \
map_projection \
drop
map_projection

ifneq ($(EXTRA_TESTS),)
REGRESS += $(EXTRA_TESTS)
endif

REGRESS += drop

srcdir=`pwd`

Expand Down
29 changes: 0 additions & 29 deletions regress/expected/expr.out
Original file line number Diff line number Diff line change
Expand Up @@ -8767,25 +8767,6 @@ SELECT * FROM cypher('issue_1988', $$
{"id": 844424930131969, "label": "Part", "properties": {"set": "set", "match": "match", "merge": "merge", "create": "create", "delete": "delete", "part_num": 123}}::vertex
(4 rows)

--
-- Test external extension function logic for fuzzystrmatch
--
SELECT * FROM create_graph('fuzzystrmatch');
NOTICE: graph "fuzzystrmatch" has been created
create_graph
--------------

(1 row)

-- These should fail with extension not installed
SELECT * FROM cypher('fuzzystrmatch', $$ RETURN soundex("hello world!") $$) AS (result agtype);
ERROR: extension fuzzystrmatch is not installed for function soundex
LINE 1: SELECT * FROM cypher('fuzzystrmatch', $$ RETURN soundex("hel...
^
SELECT * FROM cypher('fuzzystrmatch', $$ RETURN difference("hello world!", "hello world!") $$) AS (result agtype);
ERROR: extension fuzzystrmatch is not installed for function difference
LINE 1: SELECT * FROM cypher('fuzzystrmatch', $$ RETURN difference("...
^
--
-- Issue 2093: Server crashes when executing SELECT agtype_hash_cmp(agtype_in('[null, null, null, null, null]'));
--
Expand All @@ -8804,16 +8785,6 @@ SELECT agtype_hash_cmp(agtype_in('[null, null, null, null, null]'));
--
-- Cleanup
--
SELECT * FROM drop_graph('fuzzystrmatch', true);
NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to table fuzzystrmatch._ag_label_vertex
drop cascades to table fuzzystrmatch._ag_label_edge
NOTICE: graph "fuzzystrmatch" has been dropped
drop_graph
------------

(1 row)

SELECT * FROM drop_graph('issue_1988', true);
NOTICE: drop cascades to 4 other objects
DETAIL: drop cascades to table issue_1988._ag_label_vertex
Expand Down
177 changes: 177 additions & 0 deletions regress/expected/fuzzystrmatch.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
* 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.
*/
LOAD 'age';
SET search_path=ag_catalog;
SELECT create_graph('graph');
NOTICE: graph "graph" has been created
create_graph
--------------

(1 row)

-- Should error out
SELECT * FROM cypher('graph', $$ RETURN soundex("hello") $$) AS (n agtype);
ERROR: function soundex does not exist
LINE 1: SELECT * FROM cypher('graph', $$ RETURN soundex("hello") $$)...
^
HINT: If the function is from an external extension, make sure the extension is installed and the function is in the search path.
-- Create the extension in the public schema
CREATE EXTENSION fuzzystrmatch SCHEMA public;
-- Should error out
SELECT * FROM cypher('graph', $$ RETURN soundex("hello") $$) AS (n agtype);
ERROR: function soundex does not exist
LINE 1: SELECT * FROM cypher('graph', $$ RETURN soundex("hello") $$)...
^
HINT: If the function is from an external extension, make sure the extension is installed and the function is in the search path.
-- Should work
SET search_path=ag_catalog, public;
SELECT * FROM cypher('graph', $$ CREATE (:Person {name: "Jane"}),
(:Person {name: "John"}),
(:Person {name: "Jone"}),
(:Person {name: "Jack"}),
(:Person {name: "Jax"}),
(:Person {name: "Jake"}),
(:Person {name: "Julie"}),
(:Person {name: "Julius"}),
(:Person {name: "Jill"}),
(:Person {name: "Jillie"}),
(:Person {name: "Julian"})
$$) AS (n agtype);
n
---
(0 rows)

SELECT * FROM cypher('graph', $$ MATCH (p) return soundex(p.name) $$) AS (n agtype);
n
--------
"J500"
"J500"
"J500"
"J200"
"J200"
"J200"
"J400"
"J420"
"J400"
"J400"
"J450"
(11 rows)

SELECT * FROM cypher('graph', $$ MATCH (p) return levenshtein(p.name, "John") $$) AS (n agtype);
n
---
3
0
2
3
3
3
4
5
3
5
4
(11 rows)

SELECT * FROM cypher('graph', $$ MATCH (p) return difference(p.name, "John") $$) AS (n agtype);
n
---
4
4
4
3
3
3
3
2
3
3
2
(11 rows)

SELECT * FROM cypher('graph', $$ MATCH (p) return metaphone(p.name, 4) $$) AS (n agtype);
n
-------
"JN"
"JN"
"JN"
"JK"
"JKS"
"JK"
"JL"
"JLS"
"JL"
"JL"
"JLN"
(11 rows)

SELECT * FROM cypher('graph', $$ MATCH (p) return dmetaphone(p.name) $$) AS (n agtype);
n
-------
"JN"
"JN"
"JN"
"JK"
"JKS"
"JK"
"JL"
"JLS"
"JL"
"JL"
"JLN"
(11 rows)

-- Difference is basically similarity using soundex, https://www.postgresql.org/docs/current/fuzzystrmatch.html
SELECT * FROM cypher('graph', $$ MATCH (p) return p ORDER BY difference(p.name, "Jon") DESC LIMIT 3$$) AS (n agtype);
n
------------------------------------------------------------------------------------
{"id": 844424930131970, "label": "Person", "properties": {"name": "John"}}::vertex
{"id": 844424930131971, "label": "Person", "properties": {"name": "Jone"}}::vertex
{"id": 844424930131969, "label": "Person", "properties": {"name": "Jane"}}::vertex
(3 rows)

SELECT * FROM cypher('graph', $$ MATCH (p) return p ORDER BY difference(p.name, "Jak") DESC LIMIT 3$$) AS (n agtype);
n
------------------------------------------------------------------------------------
{"id": 844424930131972, "label": "Person", "properties": {"name": "Jack"}}::vertex
{"id": 844424930131973, "label": "Person", "properties": {"name": "Jax"}}::vertex
{"id": 844424930131974, "label": "Person", "properties": {"name": "Jake"}}::vertex
(3 rows)

SELECT * FROM cypher('graph', $$ MATCH (p) return p ORDER BY difference(p.name, "Jil") DESC LIMIT 3$$) AS (n agtype);
n
--------------------------------------------------------------------------------------
{"id": 844424930131975, "label": "Person", "properties": {"name": "Julie"}}::vertex
{"id": 844424930131977, "label": "Person", "properties": {"name": "Jill"}}::vertex
{"id": 844424930131978, "label": "Person", "properties": {"name": "Jillie"}}::vertex
(3 rows)

-- Clean up
SELECT drop_graph('graph', true);
NOTICE: drop cascades to 3 other objects
DETAIL: drop cascades to table graph._ag_label_vertex
drop cascades to table graph._ag_label_edge
drop cascades to table graph."Person"
NOTICE: graph "graph" has been dropped
drop_graph
------------

(1 row)

DROP EXTENSION fuzzystrmatch CASCADE;
120 changes: 120 additions & 0 deletions regress/expected/pg_trgm.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* 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.
*/
LOAD 'age';
SET search_path=ag_catalog;
SELECT create_graph('graph');
NOTICE: graph "graph" has been created
create_graph
--------------

(1 row)

-- Should error out
SELECT * FROM cypher('graph', $$ RETURN show_trgm("hello") $$) AS (n agtype);
ERROR: function show_trgm does not exist
LINE 1: SELECT * FROM cypher('graph', $$ RETURN show_trgm("hello") $...
^
HINT: If the function is from an external extension, make sure the extension is installed and the function is in the search path.
-- Create the extension in the public schema
CREATE EXTENSION pg_trgm SCHEMA public;
-- Should error out
SELECT * FROM cypher('graph', $$ RETURN show_trgm("hello") $$) AS (n agtype);
ERROR: function show_trgm does not exist
LINE 1: SELECT * FROM cypher('graph', $$ RETURN show_trgm("hello") $...
^
HINT: If the function is from an external extension, make sure the extension is installed and the function is in the search path.
-- Should work
SET search_path=ag_catalog, public;
SELECT * FROM cypher('graph', $$ CREATE (:Person {name: "Jane"}),
(:Person {name: "John"}),
(:Person {name: "Jone"}),
(:Person {name: "Jack"}),
(:Person {name: "Jax"}),
(:Person {name: "Jake"}),
(:Person {name: "Julie"}),
(:Person {name: "Julius"}),
(:Person {name: "Jill"}),
(:Person {name: "Jillie"}),
(:Person {name: "Julian"})
$$) AS (n agtype);
n
---
(0 rows)

SELECT * FROM cypher('graph', $$ MATCH (p) return show_trgm(p.name) $$) AS (n text[]);
n
-------------------------------------
{" j"," ja",ane,jan,"ne "}
{" j"," jo","hn ",joh,ohn}
{" j"," jo",jon,"ne ",one}
{" j"," ja",ack,"ck ",jac}
{" j"," ja","ax ",jax}
{" j"," ja",ake,jak,"ke "}
{" j"," ju","ie ",jul,lie,uli}
{" j"," ju",ius,jul,liu,uli,"us "}
{" j"," ji",ill,jil,"ll "}
{" j"," ji","ie ",ill,jil,lie,lli}
{" j"," ju","an ",ian,jul,lia,uli}
(11 rows)

SELECT * FROM cypher('graph', $$ MATCH (p) with p, similarity(p.name, "Jon") as sim return p.name, sim ORDER BY sim DESC $$) AS (n agtype, s real);
n | s
----------+------------
"Jone" | 0.5
"John" | 0.2857143
"Jax" | 0.14285715
"Jane" | 0.125
"Jack" | 0.125
"Jake" | 0.125
"Jill" | 0.125
"Julie" | 0.11111111
"Julius" | 0.1
"Julian" | 0.1
"Jillie" | 0.1
(11 rows)

SELECT * FROM cypher('graph', $$ MATCH (p) with p, word_similarity(p.name, "Jon") as sim return p.name, sim ORDER BY sim DESC $$) AS (n agtype, s real);
n | s
----------+------------
"Jone" | 0.6
"John" | 0.4
"Jax" | 0.25
"Jane" | 0.2
"Jack" | 0.2
"Jake" | 0.2
"Jill" | 0.2
"Julie" | 0.16666667
"Julius" | 0.14285715
"Julian" | 0.14285715
"Jillie" | 0.14285715
(11 rows)

-- Clean up
SELECT drop_graph('graph', true);
NOTICE: drop cascades to 3 other objects
DETAIL: drop cascades to table graph._ag_label_vertex
drop cascades to table graph._ag_label_edge
drop cascades to table graph."Person"
NOTICE: graph "graph" has been dropped
drop_graph
------------

(1 row)

DROP EXTENSION pg_trgm CASCADE;
Loading

0 comments on commit 3247728

Please sign in to comment.