Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pgvectorscale/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "vectorscale"
version = "0.9.0"
version = "0.9.1"
edition = "2021"

[lib]
Expand Down
67 changes: 67 additions & 0 deletions pgvectorscale/sql/vectorscale--0.9.0--0.9.1.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
CREATE OR REPLACE FUNCTION diskann_amhandler(internal)
RETURNS index_am_handler
PARALLEL SAFE IMMUTABLE STRICT COST 0.0001
LANGUAGE c
AS 'vectorscale-0.9.1', 'amhandler_wrapper';

CREATE OR REPLACE FUNCTION distance_type_cosine()
RETURNS smallint
IMMUTABLE STRICT PARALLEL SAFE
LANGUAGE c
AS 'vectorscale-0.9.1', 'distance_type_cosine_wrapper';

CREATE OR REPLACE FUNCTION distance_type_inner_product()
RETURNS smallint
IMMUTABLE STRICT PARALLEL SAFE
LANGUAGE c
AS 'vectorscale-0.9.1', 'distance_type_inner_product_wrapper';

CREATE OR REPLACE FUNCTION distance_type_l2()
RETURNS smallint
IMMUTABLE STRICT PARALLEL SAFE
LANGUAGE c
AS 'vectorscale-0.9.1', 'distance_type_l2_wrapper';

CREATE OR REPLACE FUNCTION smallint_array_overlap(
"left" smallint[],
"right" smallint[]
)
RETURNS bool
IMMUTABLE STRICT PARALLEL SAFE
LANGUAGE c
AS 'vectorscale-0.9.1', 'smallint_array_overlap_wrapper';
Comment thread
mostafa marked this conversation as resolved.

DO $$
DECLARE
expected_vector_type oid;
BEGIN
SELECT t.oid
INTO STRICT expected_vector_type
FROM pg_catalog.pg_extension e
JOIN pg_catalog.pg_type t
ON t.typnamespace = e.extnamespace
AND t.typname = 'vector'
WHERE e.extname = 'vector';

IF EXISTS (
SELECT 1
FROM pg_catalog.pg_opclass c
JOIN pg_catalog.pg_am am ON am.oid = c.opcmethod
WHERE am.amname = 'diskann'
AND c.opcnamespace = (
SELECT oid
FROM pg_catalog.pg_namespace
WHERE nspname = '@extschema@'
)
AND c.opcname IN (
'vector_cosine_ops',
'vector_l2_ops',
'vector_ip_ops'
)
AND c.opcintype IS DISTINCT FROM expected_vector_type
) THEN
RAISE EXCEPTION
'diskann: a vector operator class is not bound to pgvector''s vector type; drop the affected operator class and recreate the extension objects';
END IF;
END;
$$;
Comment on lines +35 to +68

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like this doesnt catch all variants:

  CREATE SCHEMA real_vector;
  CREATE EXTENSION vector WITH SCHEMA real_vector;

  CREATE SCHEMA evil;
  CREATE FUNCTION evil.fake_dist(real_vector.vector, real_vector.vector)
  RETURNS float8 LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE
  AS $$ SELECT 0::float8 $$;

  CREATE OPERATOR evil.<=> (
      LEFTARG  = real_vector.vector,   -- genuine
      RIGHTARG = real_vector.vector,   -- genuine
      FUNCTION = evil.fake_dist);

  CREATE EXTENSION vectorscale VERSION '0.9.0' WITH SCHEMA evil;

19 changes: 8 additions & 11 deletions pgvectorscale/src/access_method/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,13 @@ fn get_meta_page(
index_relation: &PgRelation,
opt: PgBox<TSVIndexOptions>,
) -> MetaPage {
let dimensions = index_relation.tuple_desc().get(0).unwrap().atttypmod;
unsafe {
crate::access_method::vector_type::ensure_index_vector_type(index_relation);
Comment thread
mostafa marked this conversation as resolved.
Outdated
}

let typmod = index_relation.tuple_desc().get(0).unwrap().atttypmod;
let dimensions = crate::access_method::vector_type::dimension_from_typmod(typmod)
.unwrap_or_else(|message| error!("{}", message));

let distance_type = unsafe {
let fmgr_info = index_getprocinfo(indexrel, 1, DISKANN_DISTANCE_TYPE_PROC);
Expand All @@ -265,16 +271,7 @@ fn get_meta_page(
error!("Inner product distance type is not supported with plain storage");
}

let meta_page =
unsafe { MetaPage::create(index_relation, dimensions as _, distance_type, opt) };

if meta_page.get_num_dimensions_to_index() == 0 {
error!("No dimensions to index");
}

if meta_page.get_num_dimensions_to_index() > MAX_DIMENSION {
error!("Too many dimensions to index (max is {})", MAX_DIMENSION);
}
let meta_page = unsafe { MetaPage::create(index_relation, dimensions, distance_type, opt) };

if meta_page.get_num_dimensions_to_index() > MAX_DIMENSION_NO_SBQ
&& meta_page.get_storage_type() == StorageType::Plain
Expand Down
17 changes: 15 additions & 2 deletions pgvectorscale/src/access_method/meta_page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,12 @@ impl MetaPage {
opt.num_dimensions
};

crate::access_method::vector_type::ensure_valid_dimensions(
num_dimensions,
num_dimensions_to_index,
)
.unwrap_or_else(|message| pgrx::error!("{}", message));

let bq_num_bits_per_dimension =
if opt.bq_num_bits_per_dimension == SBQ_NUM_BITS_PER_DIMENSION_DEFAULT_SENTINEL {
if (*opt).get_storage_type() == StorageType::SbqCompression
Expand Down Expand Up @@ -398,9 +404,10 @@ impl MetaPage {
/// Read the meta page for an index
pub fn fetch(index: &PgRelation) -> MetaPage {
unsafe {
crate::access_method::vector_type::ensure_index_vector_type(index);
let page = page::ReadablePage::read(index, META_BLOCK_NUMBER);
let page_type = page.get_type();
match page_type {
let meta = match page_type {
PageType::MetaV1 => {
let old_meta = MetaPageV1::page_get_meta(*page, *(*(page.get_buffer())));
let new_meta: MetaPage = (&*old_meta).into();
Expand All @@ -414,7 +421,13 @@ impl MetaPage {
PageType::MetaV2 => MetaPageV2::from_page(page).into(),
PageType::Meta => Self::load(index),
_ => pgrx::error!("Meta page is not of type Meta"),
}
};
crate::access_method::vector_type::ensure_valid_dimensions(
meta.get_num_dimensions(),
meta.get_num_dimensions_to_index(),
)
.unwrap_or_else(|message| pgrx::error!("{}", message));
meta
}
}

Expand Down
97 changes: 86 additions & 11 deletions pgvectorscale/src/access_method/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod storage;
mod storage_common;
mod upgrade_test;
mod vacuum;
mod vector_type;

/// Access method support function numbers
pub const DISKANN_DISTANCE_TYPE_PROC: u16 = 1;
Expand Down Expand Up @@ -171,6 +172,8 @@ DECLARE
have_l2_ops int;
have_ip_ops int;
have_label_ops int;
vector_schema text;
old_search_path text;
BEGIN
-- Has cosine operator class been installed previously?
SELECT count(*)
Expand Down Expand Up @@ -204,11 +207,24 @@ BEGIN
AND c.opcmethod = (SELECT oid FROM pg_catalog.pg_am am WHERE am.amname = 'diskann')
AND c.opcnamespace = (SELECT oid FROM pg_catalog.pg_namespace where nspname='@extschema@');

SELECT n.nspname
INTO STRICT vector_schema
FROM pg_catalog.pg_extension e
JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace
WHERE e.extname = 'vector';

old_search_path := pg_catalog.current_setting('search_path');
PERFORM pg_catalog.set_config(
'search_path',
Comment thread
mostafa marked this conversation as resolved.
Outdated
pg_catalog.format('%I, %I, pg_temp', vector_schema, '@extschema@'),
true
);

IF have_cos_ops = 0 THEN
CREATE OPERATOR CLASS vector_cosine_ops DEFAULT
CREATE OPERATOR CLASS @extschema@.vector_cosine_ops DEFAULT
Comment thread
mostafa marked this conversation as resolved.
Outdated
FOR TYPE vector USING diskann AS
OPERATOR 1 <=> (vector, vector) FOR ORDER BY float_ops,
FUNCTION 1 distance_type_cosine();
OPERATOR 1 <=> (vector, vector) FOR ORDER BY pg_catalog.float_ops,
FUNCTION 1 @extschema@.distance_type_cosine();
ELSIF have_l2_ops = 0 THEN
-- Upgrade from 0.4.0 to 0.5.0. Update cosine opclass to include
-- the distance_type_cosine function.
Expand All @@ -219,17 +235,39 @@ BEGIN
END IF;

IF have_l2_ops = 0 THEN
CREATE OPERATOR CLASS vector_l2_ops
CREATE OPERATOR CLASS @extschema@.vector_l2_ops
FOR TYPE vector USING diskann AS
OPERATOR 1 <-> (vector, vector) FOR ORDER BY float_ops,
FUNCTION 1 distance_type_l2();
OPERATOR 1 <-> (vector, vector) FOR ORDER BY pg_catalog.float_ops,
FUNCTION 1 @extschema@.distance_type_l2();
END IF;

IF have_ip_ops = 0 THEN
CREATE OPERATOR CLASS vector_ip_ops
CREATE OPERATOR CLASS @extschema@.vector_ip_ops
FOR TYPE vector USING diskann AS
OPERATOR 1 <#> (vector, vector) FOR ORDER BY float_ops,
FUNCTION 1 distance_type_inner_product();
OPERATOR 1 <#> (vector, vector) FOR ORDER BY pg_catalog.float_ops,
FUNCTION 1 @extschema@.distance_type_inner_product();
END IF;

PERFORM pg_catalog.set_config('search_path', old_search_path, true);

-- Refuse wrongly bound DiskANN vector opclasses left behind by older installs.
IF EXISTS (
Comment thread
mostafa marked this conversation as resolved.
Outdated
SELECT 1
FROM pg_catalog.pg_opclass c
JOIN pg_catalog.pg_am am ON am.oid = c.opcmethod
WHERE am.amname = 'diskann'
AND c.opcnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '@extschema@')
AND c.opcname IN ('vector_cosine_ops', 'vector_l2_ops', 'vector_ip_ops')
AND c.opcintype IS DISTINCT FROM (
SELECT t.oid
FROM pg_catalog.pg_extension e
JOIN pg_catalog.pg_type t
ON t.typnamespace = e.extnamespace
AND t.typname = 'vector'
WHERE e.extname = 'vector'
)
) THEN
RAISE EXCEPTION 'diskann: a vector operator class is not bound to pgvector''s vector type; drop the affected operator class and recreate the extension objects';
END IF;

-- First, check if the && operator exists for smallint[]
Expand Down Expand Up @@ -275,8 +313,29 @@ $$;
);

#[pg_guard]
pub extern "C-unwind" fn amvalidate(_opclassoid: pg_sys::Oid) -> bool {
true
pub extern "C-unwind" fn amvalidate(opclassoid: pg_sys::Oid) -> bool {
unsafe {
let tup =
pg_sys::SearchSysCache1(pg_sys::SysCacheIdentifier::CLAOID as i32, opclassoid.into());
if tup.is_null() {
return false;
}
let form = pg_sys::GETSTRUCT(tup) as pg_sys::Form_pg_opclass;
let opcname = core::ffi::CStr::from_ptr((*form).opcname.data.as_ptr())
.to_bytes()
.to_owned();
let opcintype = (*form).opcintype;
pg_sys::ReleaseSysCache(tup);

if !matches!(
opcname.as_slice(),
b"vector_cosine_ops" | b"vector_l2_ops" | b"vector_ip_ops"
) {
return true;
}

vector_type::pgvector_vector_base_oid(opcintype) == Some(opcintype)
}
}

/// Implementation of the array overlap operator (&&) for smallint arrays
Expand Down Expand Up @@ -321,6 +380,22 @@ pub fn smallint_array_overlap(left: Array<i16>, right: Array<i16>) -> bool {
mod tests {
use super::*;

#[pg_test]
fn test_vector_opclasses_validate() -> spi::Result<()> {
for opclass in ["vector_cosine_ops", "vector_l2_ops", "vector_ip_ops"] {
let oid = Spi::get_one::<pg_sys::Oid>(&format!(
"SELECT c.oid
FROM pg_catalog.pg_opclass c
JOIN pg_catalog.pg_am am ON am.oid = c.opcmethod
WHERE am.amname = 'diskann'
AND c.opcname = '{opclass}'"
))?
.expect("operator class was not installed");
assert!(amvalidate(oid), "{opclass} failed validation");
}
Ok(())
}

#[pg_test]
fn test_empty_overlap() -> spi::Result<()> {
// Test overlap with arrays containing only NULL values
Expand Down
4 changes: 2 additions & 2 deletions pgvectorscale/src/access_method/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ mod tests {

#[pg_test]
unsafe fn test_index_options_custom() -> spi::Result<()> {
Spi::run("CREATE TABLE test(encoding vector(3));
Spi::run("CREATE TABLE test(encoding vector(30));
CREATE INDEX idxtest
ON test
USING diskann(encoding)
Expand All @@ -368,7 +368,7 @@ mod tests {

#[pg_test]
unsafe fn test_index_options_custom_mem_optimized() -> spi::Result<()> {
Spi::run("CREATE TABLE test(encoding vector(3));
Spi::run("CREATE TABLE test(encoding vector(30));
CREATE INDEX idxtest
ON test
USING diskann(encoding)
Expand Down
17 changes: 15 additions & 2 deletions pgvectorscale/src/access_method/pg_vector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ impl PgVectorInternal {
let raw_slice = unsafe { self.x.as_slice(dim as _) };
raw_slice
}

pub(crate) unsafe fn reserved(&self) -> i16 {
self.unused.assume_init()
}
}

#[derive(Debug)]
Expand Down Expand Up @@ -130,6 +134,9 @@ impl PgVector {
//TODO: we are using a copy here to avoid lifetime issues and because in some cases we have to
//modify the datum in preprocess_cosine. We should find a way to avoid the copy if the vector is
//normalized and preprocess_cosine is a noop;
//
// Callers must have verified the index attribute is pgvector's vector type before reaching
// here. Detoast still assumes a well-formed varlena; layout is checked immediately after.
let detoasted = pg_sys::pg_detoast_datum_copy(datum.cast_mut_ptr());
let is_copy = !std::ptr::eq(
detoasted.cast::<PgVectorInternal>(),
Expand All @@ -140,15 +147,21 @@ impl PgVector {
assert!(is_copy, "Datum should be a copy");
let casted = detoasted.cast::<PgVectorInternal>();

// Validate the on-disk/layout dimension before any slice is constructed.
let dim = super::vector_type::checked_vector_dim(casted)
.unwrap_or_else(|message| error!("{}", message));
super::vector_type::ensure_datum_dimension(dim, meta_page.get_num_dimensions())
.unwrap_or_else(|message| error!("{}", message));

if is_index_distance
&& meta_page.get_num_dimensions() != meta_page.get_num_dimensions_to_index()
{
assert!((*casted).dim > meta_page.get_num_dimensions_to_index() as _);
(*casted).dim = meta_page.get_num_dimensions_to_index() as _;
}

let dim = (*casted).dim;
let raw_slice = unsafe { (*casted).x.as_mut_slice(dim as _) };
let dim = (*casted).dim as usize;
let raw_slice = unsafe { (*casted).x.as_mut_slice(dim) };

if meta_page.get_distance_type() == DistanceType::Cosine {
preprocess_cosine(raw_slice);
Expand Down
7 changes: 7 additions & 0 deletions pgvectorscale/src/access_method/upgrade_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,4 +334,11 @@ pub mod tests {
fn test_upgrade_from_0_8_0() {
test_upgrade_base("0.8.0", "0.12.9", "pgvectorscale", "vectorscale", "diskann");
}

#[ignore]
#[serial]
#[test]
fn test_upgrade_from_0_9_0() {
test_upgrade_base("0.9.0", "0.16.1", "pgvectorscale", "vectorscale", "diskann");
}
}
Loading
Loading