Skip to content

Commit

Permalink
Add support and tests for install-time schema relocation
Browse files Browse the repository at this point in the history
Our extension cannot be fully relocatable since its objects span
multiple schemas (we have _lantern_internal schema)
It can be (and as of this commit) is install-time relocatable
so it can be installed on a custom schema, different from the default
one. See 38.17.2 in the link below for details
https://www.postgresql.org/docs/current/extend-extensions.html#EXTEND-EXTENSIONS-RELOCATION

Note: access methods in postgres are not schema-qualified so they are
always available globally, regardless of the schema on which the
extension is installed
https://www.postgresql.org/docs/current/sql-create-access-method.html
  • Loading branch information
Ngalstyan4 committed Oct 16, 2023
1 parent 00c5b41 commit 80d9eca
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 6 deletions.
4 changes: 2 additions & 2 deletions cmake/lantern.control.template
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
comment = 'LanternDB: Fast vector embedding processing in Postgres'
comment = 'Lantern: Fast vector embedding processing in Postgres'
default_version = '@LANTERNDB_VERSION@'
module_pathname = '$libdir/lantern'
relocatable = true
relocatable = false
7 changes: 4 additions & 3 deletions sql/lantern.sql
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ CREATE OPERATOR <-> (
COMMUTATOR = '<->'
);

CREATE SCHEMA _lantern_internal;
-- operator classes
CREATE OR REPLACE FUNCTION _create_ldb_operator_classes(access_method_name TEXT) RETURNS BOOLEAN AS $$
CREATE OR REPLACE FUNCTION _lantern_internal._create_ldb_operator_classes(access_method_name TEXT) RETURNS BOOLEAN AS $$
DECLARE
dist_l2sq_ops TEXT;
dist_cos_ops TEXT;
Expand Down Expand Up @@ -107,13 +108,13 @@ BEGIN


IF hnsw_am_exists THEN
PERFORM _create_ldb_operator_classes('lantern_hnsw');
PERFORM _lantern_internal._create_ldb_operator_classes('lantern_hnsw');
RAISE WARNING 'Access method(index type) "hnsw" already exists. Creating lantern_hnsw access method';
ELSE
-- create access method
CREATE ACCESS METHOD hnsw TYPE INDEX HANDLER hnsw_handler;
COMMENT ON ACCESS METHOD hnsw IS 'LanternDB access method for vector embeddings, based on the hnsw algorithm';
PERFORM _create_ldb_operator_classes('hnsw');
PERFORM _lantern_internal._create_ldb_operator_classes('hnsw');
END IF;
END;
$BODY$
Expand Down
109 changes: 109 additions & 0 deletions test/expected/ext_relocation.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
\ir utils/small_world_array.sql
CREATE TABLE small_world (
id VARCHAR(3),
b BOOLEAN,
v REAL[3]
);
INSERT INTO small_world (id, b, v) VALUES
('000', TRUE, '{0,0,0}'),
('001', TRUE, '{0,0,1}'),
('010', FALSE, '{0,1,0}'),
('011', TRUE, '{0,1,1}'),
('100', FALSE, '{1,0,0}'),
('101', FALSE, '{1,0,1}'),
('110', FALSE, '{1,1,0}'),
('111', TRUE, '{1,1,1}');
DROP EXTENSION lantern;
\set ON_ERROR_STOP off
-- make sure the extension was dropped.
CREATE INDEX ON small_world USING hnsw (v) WITH (dim=3);
ERROR: access method "hnsw" does not exist
\set ON_ERROR_STOP on
-- test creating lantern on different schemas
CREATE SCHEMA schema1;
CREATE SCHEMA schema2;
CREATE EXTENSION lantern WITH SCHEMA schema1;
-- show all the extension functions and operators
SELECT ne.nspname AS extschema, p.proname, np.nspname AS proschema
FROM pg_catalog.pg_extension AS e
INNER JOIN pg_catalog.pg_depend AS d ON (d.refobjid = e.oid)
INNER JOIN pg_catalog.pg_proc AS p ON (p.oid = d.objid)
INNER JOIN pg_catalog.pg_namespace AS ne ON (ne.oid = e.extnamespace)
INNER JOIN pg_catalog.pg_namespace AS np ON (np.oid = p.pronamespace)
WHERE d.deptype = 'e' AND e.extname = 'lantern'
ORDER BY 1, 3;
extschema | proname | proschema
-----------+------------------------------+-------------------
schema1 | _create_ldb_operator_classes | _lantern_internal
schema1 | ldb_generic_dist | schema1
schema1 | ldb_generic_dist | schema1
schema1 | hnsw_handler | schema1
schema1 | cos_dist | schema1
schema1 | hamming_dist | schema1
schema1 | l2sq_dist | schema1
(7 rows)

-- show all the extension operators
SELECT ne.nspname AS extschema, op.oprname, np.nspname AS proschema
FROM pg_catalog.pg_extension AS e
INNER JOIN pg_catalog.pg_depend AS d ON (d.refobjid = e.oid)
INNER JOIN pg_catalog.pg_operator AS op ON (op.oid = d.objid)
INNER JOIN pg_catalog.pg_namespace AS ne ON (ne.oid = e.extnamespace)
INNER JOIN pg_catalog.pg_namespace AS np ON (np.oid = op.oprnamespace)
WHERE d.deptype = 'e' AND e.extname = 'lantern'
ORDER BY 1, 3;
extschema | oprname | proschema
-----------+---------+-----------
schema1 | <-> | schema1
schema1 | <-> | schema1
(2 rows)

SET search_path TO public, schema1;
-- extension function is accessible
SELECT l2sq_dist(ARRAY[1.0, 2.0, 3.0], ARRAY[4.0, 5.0, 6.0]);
l2sq_dist
-----------
27
(1 row)

CREATE INDEX hnsw_index ON small_world USING hnsw(v) WITH (dim=3);
INFO: done init usearch index
INFO: inserted 8 elements
INFO: done saving 8 vectors
\set ON_ERROR_STOP off
-- lantern does not support relocation.
-- Postgres will not allow it to support this since its objects span over more than one schema
ALTER EXTENSION lantern SET SCHEMA schema2;
ERROR: extension "lantern" does not support SET SCHEMA
\set ON_ERROR_STOP on
SELECT ne.nspname AS extschema, op.oprname, np.nspname AS proschema
FROM pg_catalog.pg_extension AS e
INNER JOIN pg_catalog.pg_depend AS d ON (d.refobjid = e.oid)
INNER JOIN pg_catalog.pg_operator AS op ON (op.oid = d.objid)
INNER JOIN pg_catalog.pg_namespace AS ne ON (ne.oid = e.extnamespace)
INNER JOIN pg_catalog.pg_namespace AS np ON (np.oid = op.oprnamespace)
WHERE d.deptype = 'e' AND e.extname = 'lantern'
ORDER BY 1, 3;
extschema | oprname | proschema
-----------+---------+-----------
schema1 | <-> | schema1
schema1 | <-> | schema1
(2 rows)

SET search_path TO public, schema2;
--extension access method is still accessible since access methods are not schema-qualified
CREATE INDEX hnsw_index2 ON small_world USING hnsw(v) WITH (dim=3);
INFO: done init usearch index
INFO: inserted 8 elements
INFO: done saving 8 vectors
\set ON_ERROR_STOP off
-- extension function cannot be found without schema-qualification
SELECT l2sq_dist(ARRAY[1.0, 2.0, 3.0], ARRAY[4.0, 5.0, 6.0]);
ERROR: function l2sq_dist(numeric[], numeric[]) does not exist at character 8
\set ON_ERROR_STOP on
SELECT schema1.l2sq_dist(ARRAY[1.0, 2.0, 3.0], ARRAY[4.0, 5.0, 6.0]);
l2sq_dist
-----------
27
(1 row)

2 changes: 1 addition & 1 deletion test/schedule.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
# - 'test' lines may have multiple space-separated tests. All tests in a single 'test' line will be run in parallel

test_pgvector: hnsw_vector
test: hnsw_config hnsw_correct hnsw_create hnsw_create_expr hnsw_dist_func hnsw_insert hnsw_select hnsw_todo hnsw_index_from_file hnsw_cost_estimate
test: hnsw_config hnsw_correct hnsw_create hnsw_create_expr hnsw_dist_func hnsw_insert hnsw_select hnsw_todo hnsw_index_from_file hnsw_cost_estimate ext_relocation
64 changes: 64 additions & 0 deletions test/sql/ext_relocation.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
\ir utils/small_world_array.sql

DROP EXTENSION lantern;
\set ON_ERROR_STOP off
-- make sure the extension was dropped.
CREATE INDEX ON small_world USING hnsw (v) WITH (dim=3);
\set ON_ERROR_STOP on

-- test creating lantern on different schemas
CREATE SCHEMA schema1;
CREATE SCHEMA schema2;
CREATE EXTENSION lantern WITH SCHEMA schema1;

-- show all the extension functions and operators
SELECT ne.nspname AS extschema, p.proname, np.nspname AS proschema
FROM pg_catalog.pg_extension AS e
INNER JOIN pg_catalog.pg_depend AS d ON (d.refobjid = e.oid)
INNER JOIN pg_catalog.pg_proc AS p ON (p.oid = d.objid)
INNER JOIN pg_catalog.pg_namespace AS ne ON (ne.oid = e.extnamespace)
INNER JOIN pg_catalog.pg_namespace AS np ON (np.oid = p.pronamespace)
WHERE d.deptype = 'e' AND e.extname = 'lantern'
ORDER BY 1, 3;

-- show all the extension operators
SELECT ne.nspname AS extschema, op.oprname, np.nspname AS proschema
FROM pg_catalog.pg_extension AS e
INNER JOIN pg_catalog.pg_depend AS d ON (d.refobjid = e.oid)
INNER JOIN pg_catalog.pg_operator AS op ON (op.oid = d.objid)
INNER JOIN pg_catalog.pg_namespace AS ne ON (ne.oid = e.extnamespace)
INNER JOIN pg_catalog.pg_namespace AS np ON (np.oid = op.oprnamespace)
WHERE d.deptype = 'e' AND e.extname = 'lantern'
ORDER BY 1, 3;

SET search_path TO public, schema1;

-- extension function is accessible
SELECT l2sq_dist(ARRAY[1.0, 2.0, 3.0], ARRAY[4.0, 5.0, 6.0]);

CREATE INDEX hnsw_index ON small_world USING hnsw(v) WITH (dim=3);

\set ON_ERROR_STOP off
-- lantern does not support relocation.
-- Postgres will not allow it to support this since its objects span over more than one schema
ALTER EXTENSION lantern SET SCHEMA schema2;
\set ON_ERROR_STOP on

SELECT ne.nspname AS extschema, op.oprname, np.nspname AS proschema
FROM pg_catalog.pg_extension AS e
INNER JOIN pg_catalog.pg_depend AS d ON (d.refobjid = e.oid)
INNER JOIN pg_catalog.pg_operator AS op ON (op.oid = d.objid)
INNER JOIN pg_catalog.pg_namespace AS ne ON (ne.oid = e.extnamespace)
INNER JOIN pg_catalog.pg_namespace AS np ON (np.oid = op.oprnamespace)
WHERE d.deptype = 'e' AND e.extname = 'lantern'
ORDER BY 1, 3;

SET search_path TO public, schema2;
--extension access method is still accessible since access methods are not schema-qualified
CREATE INDEX hnsw_index2 ON small_world USING hnsw(v) WITH (dim=3);

\set ON_ERROR_STOP off
-- extension function cannot be found without schema-qualification
SELECT l2sq_dist(ARRAY[1.0, 2.0, 3.0], ARRAY[4.0, 5.0, 6.0]);
\set ON_ERROR_STOP on
SELECT schema1.l2sq_dist(ARRAY[1.0, 2.0, 3.0], ARRAY[4.0, 5.0, 6.0]);

0 comments on commit 80d9eca

Please sign in to comment.