.
This commit is contained in:
parent
7d61dd7598
commit
163f2b9fa4
@ -0,0 +1,20 @@
|
|||||||
|
import { describe, expect, test } from "vitest";
|
||||||
|
|
||||||
|
import { buildBooleanFullTextQuery } from "../build-boolean-full-text-query";
|
||||||
|
|
||||||
|
describe("buildBooleanFullTextQuery", () => {
|
||||||
|
test.each([
|
||||||
|
["pr", "+pr*"],
|
||||||
|
["pr-", "+pr*"],
|
||||||
|
["-pr", "+pr*"],
|
||||||
|
["cliente prueba", "+cliente* +prueba*"],
|
||||||
|
["cliente-prueba", "+cliente* +prueba*"],
|
||||||
|
["José Pérez", "+José* +Pérez*"],
|
||||||
|
["++ -- **", ""],
|
||||||
|
["", ""],
|
||||||
|
[" ", ""],
|
||||||
|
['+ - > < ( ) ~ * " @', ""],
|
||||||
|
])("normalizes %p into %p", (input, expected) => {
|
||||||
|
expect(buildBooleanFullTextQuery(input)).toBe(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
import { describe, expect, test } from "vitest";
|
||||||
|
import { Op, type Sequelize } from "sequelize";
|
||||||
|
|
||||||
|
import { Criteria } from "../critera";
|
||||||
|
import { CriteriaToSequelizeConverter } from "../criteria-to-sequelize-converter";
|
||||||
|
|
||||||
|
const createDatabaseStub = () =>
|
||||||
|
({
|
||||||
|
escape: (value: string) => `'${value.replaceAll("'", "''")}'`,
|
||||||
|
}) as Sequelize;
|
||||||
|
|
||||||
|
describe("CriteriaToSequelizeConverter quick search", () => {
|
||||||
|
test("builds a full-text filter and score for free text search", () => {
|
||||||
|
const converter = new CriteriaToSequelizeConverter();
|
||||||
|
const criteria = Criteria.fromPrimitives([], "name", "asc", 10, 0, "pr-");
|
||||||
|
|
||||||
|
const options = converter.convert(criteria, {
|
||||||
|
searchableFields: ["name", "trade_name"],
|
||||||
|
sortableFields: ["name"],
|
||||||
|
enableFullText: true,
|
||||||
|
database: createDatabaseStub(),
|
||||||
|
strictMode: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(options.attributes).toEqual({
|
||||||
|
include: [[expect.objectContaining({ val: expect.stringContaining("'+pr*'") }), "score"]],
|
||||||
|
});
|
||||||
|
expect(options.order).toEqual([
|
||||||
|
[expect.objectContaining({ val: "score" }), "DESC"],
|
||||||
|
["name", "ASC"],
|
||||||
|
]);
|
||||||
|
expect(options.where).toEqual({
|
||||||
|
[Op.and]: expect.any(Array),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("omits MATCH, score and score ordering when the normalized search is empty", () => {
|
||||||
|
const converter = new CriteriaToSequelizeConverter();
|
||||||
|
const criteria = Criteria.fromPrimitives([], "name", "asc", 10, 0, "++ -- **");
|
||||||
|
|
||||||
|
const options = converter.convert(criteria, {
|
||||||
|
searchableFields: ["name", "trade_name"],
|
||||||
|
sortableFields: ["name"],
|
||||||
|
enableFullText: true,
|
||||||
|
database: createDatabaseStub(),
|
||||||
|
strictMode: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(options.attributes).toBeUndefined();
|
||||||
|
expect(options.order).toEqual([["name", "ASC"]]);
|
||||||
|
expect(options.where).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
const FULL_TEXT_TOKEN_PATTERN = /[\p{L}\p{N}]+/gu;
|
||||||
|
|
||||||
|
export function buildBooleanFullTextQuery(search: string): string {
|
||||||
|
if (!search) return "";
|
||||||
|
|
||||||
|
const terms = search.normalize("NFKC").match(FULL_TEXT_TOKEN_PATTERN) ?? [];
|
||||||
|
|
||||||
|
return terms.map((term) => `+${term}*`).join(" ");
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ import type {
|
|||||||
CriteriaMappings,
|
CriteriaMappings,
|
||||||
ICriteriaToOrmConverter,
|
ICriteriaToOrmConverter,
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
|
import { buildBooleanFullTextQuery } from "./build-boolean-full-text-query.js";
|
||||||
import { appendOrder, normalizeOrder, prependOrder } from "./utils";
|
import { appendOrder, normalizeOrder, prependOrder } from "./utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,11 +67,8 @@ export class CriteriaToSequelizeConverter implements ICriteriaToOrmConverter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const booleanTerm = term
|
const booleanTerm = buildBooleanFullTextQuery(term);
|
||||||
.split(/\s+/)
|
if (!booleanTerm) return;
|
||||||
.filter(Boolean)
|
|
||||||
.map((word) => `+${word}*`)
|
|
||||||
.join(" ");
|
|
||||||
|
|
||||||
const qualifiedFields = searchableFields.map((field) =>
|
const qualifiedFields = searchableFields.map((field) =>
|
||||||
this.qualifyRootColumn(field, fullTextTableAlias)
|
this.qualifyRootColumn(field, fullTextTableAlias)
|
||||||
@ -216,7 +214,6 @@ export class CriteriaToSequelizeConverter implements ICriteriaToOrmConverter {
|
|||||||
return Op.lt;
|
return Op.lt;
|
||||||
case "LOWER_THAN_OR_EQUAL":
|
case "LOWER_THAN_OR_EQUAL":
|
||||||
return Op.lte;
|
return Op.lte;
|
||||||
case "EQUALS":
|
|
||||||
default:
|
default:
|
||||||
return Op.eq;
|
return Op.eq;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
export * from "./critera";
|
export * from "./critera";
|
||||||
|
export * from "./build-boolean-full-text-query";
|
||||||
export * from "./criteria-from-url-converter";
|
export * from "./criteria-from-url-converter";
|
||||||
export * from "./criteria-to-sequelize-converter";
|
export * from "./criteria-to-sequelize-converter";
|
||||||
export * from "./defaults";
|
export * from "./defaults";
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user