Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- **`queryVector()` with Vamana / HNSW RaBitQ query params** (#193)
- `ZVecVectorQuery::setVamanaParams()` and `setHnswRabitqParams()` previously created an `HnswQueryParams` object, so `queryVector()` on Vamana/RaBitQ indexes was always rejected by the engine (`query params type does not match the index type of vector field`).
- FFI: added `zvec_vector_query_set_vamana_ef_search()` (`VamanaQueryParams`) and `zvec_vector_query_set_hnsw_rabitq_ef()` (`HnswRabitqQueryParams`), mirroring `apply_query_params`; the legacy `query()` path was unaffected.
- Added regression tests `test_vector_query_vamana_params.phpt` and `test_vector_query_rabitq_params.phpt` (RaBitQ runs on Linux x86_64 only).

### Fixed

- **Deprecated index creation warnings** (#169)
- The FFI bindings’ `createHnswIndex()`, `createHnswRabitqIndex()`, `createFlatIndex()`, and `createIvfIndex()` now emit `E_USER_DEPRECATED` before delegating to the unified `createIndex()` API.
- **Regression gate against zvec v0.6.0** (#175)
Expand Down
19 changes: 19 additions & 0 deletions docs/helpers/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,22 @@ FFI call via `self::checkStatus()`.

**Solution:** only `close()` if the collection may be reopened; never use
an object after `destroy()`.

---

### queryVector() enforces query-param/index-type match

**Problem:** `queryVector()` rejects a query when the params' index type
(`HnswQueryParams`, `VamanaQueryParams`, ...) differs from the field's index
type (validation in `zvec/src/db/index/common/query.cc`).
`setVamanaParams()`/`setHnswRabitqParams()` used to call
`zvec_vector_query_set_hnsw_ef` (HnswQueryParams), so queryVector() on
Vamana/RaBitQ indexes always failed with INVALID_ARGUMENT.

**Solution:** dedicated FFI setters create the matching params class:
`zvec_vector_query_set_vamana_ef_search` → `VamanaQueryParams`,
`zvec_vector_query_set_hnsw_rabitq_ef` → `HnswRabitqQueryParams`.
The legacy `query()` path was never affected (it rebuilds params per
`queryParamType`).

**Reference:** issue #193.
12 changes: 12 additions & 0 deletions ffi/zvec_ffi.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2067,6 +2067,18 @@ void zvec_vector_query_set_hnsw_ef(zvec_vector_query_t q, int ef) {
holder->query.target_.query_params_ = std::make_shared<HnswQueryParams>(ef);
}

void zvec_vector_query_set_hnsw_rabitq_ef(zvec_vector_query_t q, int ef) {
if (!q) return;
auto* holder = static_cast<VectorQueryHolder*>(q);
holder->query.target_.query_params_ = std::make_shared<HnswRabitqQueryParams>(ef);
}

void zvec_vector_query_set_vamana_ef_search(zvec_vector_query_t q, int ef_search) {
if (!q) return;
auto* holder = static_cast<VectorQueryHolder*>(q);
holder->query.target_.query_params_ = std::make_shared<VamanaQueryParams>(ef_search);
}

void zvec_vector_query_set_ivf_nprobe(zvec_vector_query_t q, int nprobe) {
if (!q) return;
auto* holder = static_cast<VectorQueryHolder*>(q);
Expand Down
2 changes: 2 additions & 0 deletions ffi/zvec_ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,8 @@ void zvec_vector_query_set_include_vector(zvec_vector_query_t q, int include);
void zvec_vector_query_set_filter(zvec_vector_query_t q, const char* filter);
void zvec_vector_query_set_output_fields(zvec_vector_query_t q, const char** fields, int count);
void zvec_vector_query_set_hnsw_ef(zvec_vector_query_t q, int ef);
void zvec_vector_query_set_hnsw_rabitq_ef(zvec_vector_query_t q, int ef);
void zvec_vector_query_set_vamana_ef_search(zvec_vector_query_t q, int ef_search);
void zvec_vector_query_set_ivf_nprobe(zvec_vector_query_t q, int nprobe);
void zvec_vector_query_set_flat_mode(zvec_vector_query_t q);
void zvec_vector_query_set_radius(zvec_vector_query_t q, float radius);
Expand Down
2 changes: 2 additions & 0 deletions ffi/zvec_ffi_php.h
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,8 @@ void zvec_vector_query_set_include_vector(zvec_vector_query_t q, int include);
void zvec_vector_query_set_filter(zvec_vector_query_t q, const char* filter);
void zvec_vector_query_set_output_fields(zvec_vector_query_t q, const char** fields, int count);
void zvec_vector_query_set_hnsw_ef(zvec_vector_query_t q, int ef);
void zvec_vector_query_set_hnsw_rabitq_ef(zvec_vector_query_t q, int ef);
void zvec_vector_query_set_vamana_ef_search(zvec_vector_query_t q, int ef_search);
void zvec_vector_query_set_ivf_nprobe(zvec_vector_query_t q, int nprobe);
void zvec_vector_query_set_flat_mode(zvec_vector_query_t q);
void zvec_vector_query_set_radius(zvec_vector_query_t q, float radius);
Expand Down
4 changes: 2 additions & 2 deletions src/ZVecVectorQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ public function setHnswRabitqParams(int $ef): self
{
$this->queryParamType = ZVec::QUERY_PARAM_HNSW_RABITQ;
$this->hnswEf = $ef;
self::ffi()->zvec_vector_query_set_hnsw_ef($this->handle, $ef);
self::ffi()->zvec_vector_query_set_hnsw_rabitq_ef($this->handle, $ef);
return $this;
}

Expand All @@ -163,7 +163,7 @@ public function setVamanaParams(int $efSearch): self
{
$this->queryParamType = ZVec::QUERY_PARAM_VAMANA;
$this->hnswEf = $efSearch;
self::ffi()->zvec_vector_query_set_hnsw_ef($this->handle, $efSearch);
self::ffi()->zvec_vector_query_set_vamana_ef_search($this->handle, $efSearch);
return $this;
}

Expand Down
64 changes: 64 additions & 0 deletions tests/test_vector_query_rabitq_params.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
--TEST--
Vector query params: queryVector() with setHnswRabitqParams() on an HNSW RaBitQ index (issue #193)
--SKIPIF--
<?php
if (!extension_loaded('ffi')) die('skip FFI extension not available');
if (PHP_OS_FAMILY !== 'Linux' || php_uname('m') !== 'x86_64') die('skip RaBitQ supported on Linux x86_64 only');
?>
--FILE--
<?php
require_once __DIR__ . '/../src/ZVec.php';
ZVec::init(logType: ZVec::LOG_CONSOLE, logLevel: ZVec::LOG_WARN);

$path = __DIR__ . '/../test_dbs/rabitq_query_params_' . uniqid();
try {
$schema = new ZVecSchema('test');
$schema->addInt64('id');
$schema->addVectorFp32('vec', dimension: 64, metricType: ZVecSchema::METRIC_IP);
$coll = ZVec::create($path, $schema);

$coll->createIndex('vec', ZVecIndexParams::forHnswRabitq(
metricType: ZVecSchema::METRIC_IP,
m: 50,
efConstruction: 500,
));

$data = [];
for ($i = 0; $i < 10; $i++) {
$vec = [];
for ($j = 0; $j < 64; $j++) {
$vec[] = $j === $i ? 1.0 : 0.0;
}
$data[] = $vec;
$doc = new ZVecDoc('doc' . $i);
$doc->setInt64('id', $i);
$doc->setVectorFp32('vec', $vec);
$coll->insert($doc);
}
$coll->optimize();

$query = new ZVecVectorQuery('vec', $data[0]);
$query->setHnswRabitqParams(ef: 100);
$results = $coll->queryVector($query);
assert(count($results) >= 1, 'Expected at least 1 result');
echo "queryVector with HNSW RaBitQ params returned " . count($results) . " results, top: " . $results[0]->getPk() . "\n";

try {
$bad = new ZVecVectorQuery('vec', $data[0]);
$bad->setHnswParams(ef: 200);
$coll->queryVector($bad);
echo "UNEXPECTED: HNSW params on RaBitQ index should have thrown\n";
} catch (ZVecException $e) {
echo "HNSW params on RaBitQ index correctly rejected: " . $e->getErrorCodeString() . "\n";
}

$coll->close();
echo "PASS: queryVector with setHnswRabitqParams works\n";
} finally {
exec("rm -rf " . escapeshellarg($path));
}
?>
--EXPECTF--
%AqueryVector with HNSW RaBitQ params returned %d results, top: doc0
%AHNSW params on RaBitQ index correctly rejected: INVALID_ARGUMENT
%APASS: queryVector with setHnswRabitqParams works
58 changes: 58 additions & 0 deletions tests/test_vector_query_vamana_params.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
--TEST--
Vector query params: queryVector() with setVamanaParams() on a Vamana index (issue #193)
--SKIPIF--
<?php if (extension_loaded('zvec')) die('skip This test uses ZVecIndexParams which only works via FFI'); ?>
<?php if (!extension_loaded('ffi')) die('skip FFI extension not available'); ?>
--FILE--
<?php
require_once __DIR__ . '/../src/ZVec.php';
ZVec::init(logType: ZVec::LOG_CONSOLE, logLevel: ZVec::LOG_WARN);

$path = __DIR__ . '/../test_dbs/vamana_query_params_' . uniqid();
try {
$schema = new ZVecSchema('vamana_test');
$schema->addInt64('id', nullable: false)
->addVectorFp32('v', dimension: 4, metricType: ZVecSchema::METRIC_IP);

$c = ZVec::create($path, $schema);

$c->createIndex('v', ZVecIndexParams::forVamana(
metricType: ZVecSchema::METRIC_IP,
maxDegree: 32,
searchListSize: 50,
alpha: 1.0,
saturateGraph: false,
));

$c->insert(
(new ZVecDoc('doc1'))->setInt64('id', 1)->setVectorFp32('v', [1.0, 0.0, 0.0, 0.0]),
(new ZVecDoc('doc2'))->setInt64('id', 2)->setVectorFp32('v', [0.0, 1.0, 0.0, 0.0]),
(new ZVecDoc('doc3'))->setInt64('id', 3)->setVectorFp32('v', [0.0, 0.0, 1.0, 0.0]),
);
$c->optimize();

$vq = new ZVecVectorQuery('v', [1.0, 0.1, 0.0, 0.0]);
$vq->setVamanaParams(efSearch: 50);
$results = $c->queryVector($vq);
assert(count($results) === 3, 'Expected 3 results');
echo "queryVector with Vamana params returned " . count($results) . " results, top: " . $results[0]->getPk() . "\n";

try {
$bad = new ZVecVectorQuery('v', [1.0, 0.1, 0.0, 0.0]);
$bad->setHnswParams(ef: 200);
$c->queryVector($bad);
echo "UNEXPECTED: HNSW params on Vamana index should have thrown\n";
} catch (ZVecException $e) {
echo "HNSW params on Vamana index correctly rejected: " . $e->getErrorCodeString() . "\n";
}

$c->close();
echo "PASS: queryVector with setVamanaParams works\n";
} finally {
exec("rm -rf " . escapeshellarg($path));
}
?>
--EXPECT--
queryVector with Vamana params returned 3 results, top: doc1
HNSW params on Vamana index correctly rejected: INVALID_ARGUMENT
PASS: queryVector with setVamanaParams works
Loading