feat: support prefix matching and must_not clause (#2441)

This commit is contained in:
BubbleCal
2025-06-19 10:32:32 +08:00
committed by GitHub
parent c72f6770fd
commit cbb5a841b1
6 changed files with 124 additions and 33 deletions
+30 -8
View File
@@ -1650,13 +1650,25 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
expect(resultSet.has("fob")).toBe(true);
expect(resultSet.has("fo")).toBe(true);
expect(resultSet.has("food")).toBe(true);
const prefixResults = await table
.search(
new MatchQuery("foo", "text", { fuzziness: 3, prefixLength: 3 }),
)
.toArray();
expect(prefixResults.length).toBe(2);
const resultSet2 = new Set(prefixResults.map((r) => r.text));
expect(resultSet2.has("foo")).toBe(true);
expect(resultSet2.has("food")).toBe(true);
});
test("full text search boolean query", async () => {
const db = await connect(tmpDir.name);
const data = [
{ text: "hello world", vector: [0.1, 0.2, 0.3] },
{ text: "goodbye world", vector: [0.4, 0.5, 0.6] },
{ text: "The cat and dog are playing" },
{ text: "The cat is sleeping" },
{ text: "The dog is barking" },
{ text: "The dog chases the cat" },
];
const table = await db.createTable("test", data);
await table.createIndex("text", {
@@ -1666,22 +1678,32 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
const shouldResults = await table
.search(
new BooleanQuery([
[Occur.Should, new MatchQuery("hello", "text")],
[Occur.Should, new MatchQuery("goodbye", "text")],
[Occur.Should, new MatchQuery("cat", "text")],
[Occur.Should, new MatchQuery("dog", "text")],
]),
)
.toArray();
expect(shouldResults.length).toBe(2);
expect(shouldResults.length).toBe(4);
const mustResults = await table
.search(
new BooleanQuery([
[Occur.Must, new MatchQuery("hello", "text")],
[Occur.Must, new MatchQuery("world", "text")],
[Occur.Must, new MatchQuery("cat", "text")],
[Occur.Must, new MatchQuery("dog", "text")],
]),
)
.toArray();
expect(mustResults.length).toBe(1);
expect(mustResults.length).toBe(2);
const mustNotResults = await table
.search(
new BooleanQuery([
[Occur.Must, new MatchQuery("cat", "text")],
[Occur.MustNot, new MatchQuery("dog", "text")],
]),
)
.toArray();
expect(mustNotResults.length).toBe(1);
});
test.each([
+6 -1
View File
@@ -812,10 +812,12 @@ export enum Operator {
*
* - `Must`: The term must be present in the document.
* - `Should`: The term should contribute to the document score, but is not required.
* - `MustNot`: The term must not be present in the document.
*/
export enum Occur {
Must = "MUST",
Should = "SHOULD",
Must = "MUST",
MustNot = "MUST_NOT",
}
/**
@@ -856,6 +858,7 @@ export class MatchQuery implements FullTextQuery {
* - `fuzziness`: The fuzziness level for the query (default is 0).
* - `maxExpansions`: The maximum number of terms to consider for fuzzy matching (default is 50).
* - `operator`: The logical operator to use for combining terms in the query (default is "OR").
* - `prefixLength`: The number of beginning characters being unchanged for fuzzy matching.
*/
constructor(
query: string,
@@ -865,6 +868,7 @@ export class MatchQuery implements FullTextQuery {
fuzziness?: number;
maxExpansions?: number;
operator?: Operator;
prefixLength?: number;
},
) {
let fuzziness = options?.fuzziness;
@@ -878,6 +882,7 @@ export class MatchQuery implements FullTextQuery {
fuzziness,
options?.maxExpansions ?? 50,
options?.operator ?? Operator.Or,
options?.prefixLength ?? 0,
);
}
+2
View File
@@ -335,6 +335,7 @@ impl JsFullTextQuery {
fuzziness: Option<u32>,
max_expansions: u32,
operator: String,
prefix_length: u32,
) -> napi::Result<Self> {
Ok(Self {
inner: MatchQuery::new(query)
@@ -347,6 +348,7 @@ impl JsFullTextQuery {
napi::Error::from_reason(format!("Invalid operator: {}", e))
})?,
)
.with_prefix_length(prefix_length)
.into(),
})
}