Repository navigation
Description
Problem
Neo4j 2026.01 added the Cypher 25 SEARCH subclause for vector indexes, and Neo4j 2026.04 deprecated db.index.vector.queryNodes() in its favour (SEARCH):
MATCH (d:Doc)
SEARCH d IN (VECTOR INDEX docs FOR $q WHERE d.lang = 'en' LIMIT 10) SCORE AS s
RETURN d.name, sArcadeDB's grammar has the rule (searchClause, Cypher25Parser.g4 L177-L179, used by matchClause at L233), but nothing builds or runs it: visitMatchClause in CypherASTBuilder never reads ctx.searchClause(). So the clause is parsed and dropped. Without SCORE AS that is a silent wrong answer: the MATCH runs as if SEARCH were not there and returns every node of the label, not the LIMIT nearest ones. With SCORE AS the query fails, because the score variable was never bound.
Evidence
Upstream main 1e30c60371, embedded, JDK 25, the program below: 50 Doc vertices with random 4-dimensional vectors, an LSM_VECTOR index Doc[embedding] (COSINE), one query vector $q.
| query | result |
|---|---|
CALL db.index.vector.queryNodes('Doc[embedding]', 3, $q) YIELD node, score RETURN node.name AS name, score |
3 rows, ok |
MATCH (d:Doc) SEARCH d IN (VECTOR INDEX `Doc[embedding]` FOR $q LIMIT 3) RETURN d.name AS name |
50 rows (every Doc), no error |
MATCH (d:Doc) SEARCH d IN (VECTOR INDEX `Doc[embedding]` FOR $q LIMIT 3) SCORE AS s RETURN d.name AS name, s |
CommandSemanticException: UndefinedVariable: Variable 's' not defined |
the same with WHERE d.name <> 'd0' inside the parentheses |
the same exception |
Expected
SEARCH runs on the LSM_VECTOR index it names: FOR is the query vector, LIMIT is k, SCORE AS binds the similarity score (Neo4j returns 0.0 to 1.0, highest first, like queryNodes here), and a WHERE inside the parentheses filters within the index search, so LIMIT 10 returns 10 matching rows. That last part is the Neo4j-standard answer to #10685 (queryNodes filters only after the top k). Until it is implemented, refusing a SEARCH clause with a clear error would at least stop the wrong answer.
Related: #10678 asks for Neo4j's CREATE VECTOR INDEX (on main it is refused: Only standard, RANGE and TEXT index types are supported), #10685. I can implement SEARCH for vector indexes, with the in-index filter, in a PR if you agree.
CypherVectorProbe.java (run: javac -cp 'lib/*' CypherVectorProbe.java && java -cp 'lib/*:.' CypherVectorProbe /tmp/dbprobe)
// What ArcadeDB's openCypher does with Neo4j's vector syntax (2026-10-27): the db.index.vector.queryNodes procedure, the Cypher 25 SEARCH clause
// (Neo4j 2026.01+, the replacement of queryNodes since Neo4j 2026.04) and CREATE VECTOR INDEX. Prints one CHECK/INFO line per case; never exits nonzero
// on an unsupported feature (that is the finding), only on a setup failure.
import com.arcadedb.database.Database;
import com.arcadedb.database.DatabaseFactory;
import com.arcadedb.query.sql.executor.Result;
import com.arcadedb.query.sql.executor.ResultSet;
import java.util.*;
public class CypherVectorProbe {
static List<Result> run(Database db, String q, Map<String, Object> p) {
List<Result> out = new ArrayList<>();
try (ResultSet rs = db.query("opencypher", q, p)) { while (rs.hasNext()) out.add(rs.next()); }
return out;
}
public static void main(String[] a) {
try (DatabaseFactory f = new DatabaseFactory(a[0])) {
if (f.exists()) f.open().drop();
try (Database db = f.create()) {
final int n = 50, dim = 4; final Random r = new Random(7);
db.command("sql", "CREATE VERTEX TYPE Doc"); db.command("sql", "CREATE PROPERTY Doc.name STRING"); db.command("sql", "CREATE PROPERTY Doc.embedding ARRAY_OF_FLOATS");
db.transaction(() -> { for (int i = 0; i < n; i++) { float[] v = new float[dim]; for (int j = 0; j < dim; j++) v[j] = (float) r.nextGaussian();
db.command("sql", "INSERT INTO Doc SET name = ?, embedding = ?", "d" + i, v); } });
db.command("sql", "CREATE INDEX ON Doc (embedding) LSM_VECTOR METADATA {\"dimensions\": 4, \"similarity\": \"COSINE\"}");
final List<Float> q = List.of(0.5f, -0.2f, 0.1f, 0.9f); final Map<String, Object> p = Map.of("q", q);
String[][] cases = {
{"queryNodes", "CALL db.index.vector.queryNodes('Doc[embedding]', 3, $q) YIELD node, score RETURN node.name AS name, score"},
{"SEARCH", "MATCH (d:Doc) SEARCH d IN (VECTOR INDEX `Doc[embedding]` FOR $q LIMIT 3) SCORE AS s RETURN d.name AS name, s"},
{"SEARCH, no SCORE", "MATCH (d:Doc) SEARCH d IN (VECTOR INDEX `Doc[embedding]` FOR $q LIMIT 3) RETURN d.name AS name"},
{"SEARCH+WHERE", "MATCH (d:Doc) SEARCH d IN (VECTOR INDEX `Doc[embedding]` FOR $q WHERE d.name <> 'd0' LIMIT 3) SCORE AS s RETURN d.name AS name, s"},
};
for (String[] c : cases) {
try { List<Result> rows = run(db, c[1], p);
System.out.println("\n" + (rows.size() == 3 ? "CHECK ok " : "CHECK FAIL ") + c[0] + ": " + rows.size() + " rows (expected 3 of " + n + ")" + (rows.isEmpty() ? "" : "; first " + rows.get(0).toJSON()));
} catch (Exception e) { System.out.println("INFO unsupported " + c[0] + ": " + e.getClass().getSimpleName() + ": " + String.valueOf(e.getMessage()).split("\n")[0]); }
}
try { db.command("opencypher", "CREATE VECTOR INDEX v2 IF NOT EXISTS FOR (n:Doc2) ON n.embedding OPTIONS {indexConfig: {`vector.dimensions`: 4, `vector.similarity_function`: 'cosine'}}");
System.out.println("INFO CREATE VECTOR INDEX accepted; Doc2 index exists: " + (db.getSchema().existsType("Doc2") ? db.getSchema().getType("Doc2").getAllIndexes(false).size() : 0));
} catch (Exception e) { System.out.println("INFO unsupported CREATE VECTOR INDEX: " + e.getClass().getSimpleName() + ": " + String.valueOf(e.getMessage()).split("\n")[0]); }
}
}
System.out.println("RESULT java=" + System.getProperty("java.version"));
}
}
Activity
fix(#10776): the Cypher 25 SEARCH clause runs its vector index search…
tae898 commentedon Oct 27, 2026
Verified on
8b99f38712(upstream main, which contains the merge of #10783,086c36e968), with jars from the CI build of that commit.The probe of this issue (50
Docvertices,LSM_VECTORCOSINE), JDK 21.0.12 and 25.0.4, same lines on both:1e30c60371)8b99f38712)MATCH ... SEARCH ... LIMIT 3withoutSCORE ASSCORE AS sUndefinedVariable: Variable 's' not defineds0.9480383 (equal toqueryNodes' score)WHERE d.name <> 'd0'inside the parenthesesAt scale (SIFT 100k, an indexed
grpproperty, 200 queries per selectivity, recall@10 against the exact top 10 over the matching rows, JDK 25), the merge build reproduces what the PR head showed:SEARCH ... WHERE d.grp < $t LIMIT 10returns 10 rows at every selectivity, with recall@10 0.9955 (50%), 0.9995 (10%), 0.9995 (1%) and 1.0000 (0.1%); on1993631cad, before the merge, the same query returned all 100,000 nodes. Thanks.Merge branch 'main' into feat/10685-cypher-querynodes-filter-argument
robfrank commentedon Oct 28, 2026
Fixed in #10783. The Cypher 25
SEARCH ... IN (VECTOR INDEX ...)subclause of aMATCHwas parsed and then dropped, so the query returned every node and theSCORE ASvariable was undefined. It now runs the vector index search first, binds the node and its score, and theMATCHpattern andWHEREthen filter those nearest results; the innerWHEREis applied per candidate and the search widens until k matching rows are found.Still open: #10782 (the widening stops at 100k candidates per index, so a very selective inner
WHEREcan return fewer than k rows); Neo4j-named indexes (CREATE VECTOR INDEX docs ...) are still refused, tracked in #10678.❤️ Thanks for helping make ArcadeDB better! ArcadeDB is free and open source, sustained by its community. If it's useful to you or your company, please consider becoming a sponsor to keep development going.
fix(#10776): the Cypher 25 SEARCH clause runs its vector index search…