Skip to content

Commit

Permalink
Merge pull request #78 from aleksasiriski/ms/refactor/search
Browse files Browse the repository at this point in the history
refactor(search): fuzzySearchFilters with variable dbfields
  • Loading branch information
aleksasiriski authored Dec 19, 2024
2 parents 6ca06ee + 2ad0751 commit f14aa1c
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 65 deletions.
12 changes: 6 additions & 6 deletions src/lib/server/db/employee.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { or, eq, sql, and } from 'drizzle-orm';
import { employee, employeeEntry, employeeExit } from './schema/employee';
import { StateInside, StateOutside, type State } from '$lib/types/state';
import { fuzzyConcatSearchFilters, fuzzySearchFilters } from './fuzzysearch';
import { fuzzySearchFilters } from './fuzzysearch';
import { isInside } from '../isInside';
import { DB as db } from './connect';
import { capitalizeString, sanitizeString } from '$lib/utils/sanitize';
Expand Down Expand Up @@ -70,11 +70,11 @@ export async function getEmployees(
or(
...(nonEmptySearchQuery
? [
...fuzzySearchFilters(employee.email, nonEmptySearchQuery, 1, true),
...fuzzySearchFilters(employee.fname, nonEmptySearchQuery, 2),
...fuzzySearchFilters(employee.lname, nonEmptySearchQuery, 2),
...fuzzyConcatSearchFilters(employee.fname, employee.lname, nonEmptySearchQuery, 3),
...fuzzyConcatSearchFilters(employee.lname, employee.fname, nonEmptySearchQuery, 3)
...fuzzySearchFilters([employee.email], nonEmptySearchQuery),
...fuzzySearchFilters([employee.fname], nonEmptySearchQuery, { distance: 2 }),
...fuzzySearchFilters([employee.lname], nonEmptySearchQuery, { distance: 2 }),
...fuzzySearchFilters([employee.fname, employee.lname], nonEmptySearchQuery, { distance: 3 }),
...fuzzySearchFilters([employee.lname, employee.fname], nonEmptySearchQuery, { distance: 3 })
]
: [])
)
Expand Down
79 changes: 26 additions & 53 deletions src/lib/server/db/fuzzysearch.ts
Original file line number Diff line number Diff line change
@@ -1,72 +1,45 @@
import { sql, ilike, type Column, type SQL } from 'drizzle-orm';

type FuzzySearchFiltersOptions = {
distance?: number;
substr?: boolean;
};

export function fuzzySearchFilters(
dbField: Column,
dbFields: Column[],
searchQuery: string,
distance: number,
substr: boolean = false
opts: FuzzySearchFiltersOptions = {}
): SQL[] {
// Assert dbField is valid
if (dbField === null || dbField === undefined) {
throw new Error('Invalid dbField');
// Assert dbFields are valid
if (dbFields === null || dbFields === undefined || dbFields.length === 0) {
throw new Error('Invalid dbFields');
}

// Assert searchQuery is valid
if (searchQuery === null || searchQuery === undefined || searchQuery === '') {
throw new Error('Invalid searchQuery');
}

// Assert distance is valid
if (distance === null || distance === undefined) {
throw new Error('Invalid distance');
}

// Assert substr is valid
if (substr === null || substr === undefined) {
throw new Error('Invalid substr');
}

return [
sql`LEVENSHTEIN(LOWER(${dbField}), ${searchQuery}) <= ${distance}`,
ilike(dbField, `${substr ? '%' : ''}${searchQuery}%`)
];
}

export function fuzzyConcatSearchFilters(
dbField1: Column,
dbField2: Column,
searchQuery: string,
distance: number,
substr: boolean = false
): SQL[] {
// Assert dbField1 is valid
if (dbField1 === null || dbField1 === undefined) {
throw new Error('Invalid dbField1');
}

// Assert dbField2 is valid
if (dbField2 === null || dbField2 === undefined) {
throw new Error('Invalid dbField2');
// Assert substr option is valid
if (opts.substr === null) {
throw new Error('Invalid substr option');
}

// Assert searchQuery is valid
if (searchQuery === null || searchQuery === undefined || searchQuery === '') {
throw new Error('Invalid searchQuery');
// Assert distance option is valid
if (opts.distance === null || (opts.distance !== undefined && opts.distance <= 0)) {
throw new Error('Invalid distance option');
}

// Assert distance is valid
if (distance === null || distance === undefined) {
throw new Error('Invalid distance');
}
const concatQuery = dbFields
.map((field) => sql`${field}`)
.reduce((prev, curr) => sql`${prev} || ' ' || ${curr}`);

// Assert substr is valid
if (substr === null || substr === undefined) {
throw new Error('Invalid substr');
}
// @ts-expect-error because there is no typedef for sql as first param in ilike function
const ilikeFilter = ilike(concatQuery, `${opts.substr ? '%' : ''}${searchQuery}%`);
const levenshteinFilter =
opts.distance !== undefined
? [sql`LEVENSHTEIN(LOWER(${concatQuery}), ${searchQuery}) <= ${opts.distance}`]
: [];

return [
sql`LEVENSHTEIN(LOWER(${dbField1}) || ' ' || LOWER(${dbField2}), ${searchQuery}) <= ${distance}`,
// @ts-expect-error because there is no typedef for sql as first param in ilike function
ilike(sql`${dbField1} || ' ' || ${dbField2}`, `${substr ? '%' : ''}${searchQuery}%`)
];
return [ilikeFilter, ...levenshteinFilter];
}
12 changes: 6 additions & 6 deletions src/lib/server/db/student.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { or, eq, sql, and } from 'drizzle-orm';
import { student, studentEntry, studentExit } from './schema/student';
import { StateInside, StateOutside, type State } from '$lib/types/state';
import { fuzzyConcatSearchFilters, fuzzySearchFilters } from './fuzzysearch';
import { fuzzySearchFilters } from './fuzzysearch';
import { isInside } from '../isInside';
import { DB as db } from './connect';
import { capitalizeString, sanitizeString } from '$lib/utils/sanitize';
Expand Down Expand Up @@ -70,11 +70,11 @@ export async function getStudents(
or(
...(nonEmptySearchQuery
? [
...fuzzySearchFilters(student.index, nonEmptySearchQuery, 1, true),
...fuzzySearchFilters(student.fname, nonEmptySearchQuery, 2),
...fuzzySearchFilters(student.lname, nonEmptySearchQuery, 2),
...fuzzyConcatSearchFilters(student.fname, student.lname, nonEmptySearchQuery, 3),
...fuzzyConcatSearchFilters(student.lname, student.fname, nonEmptySearchQuery, 3)
...fuzzySearchFilters([student.index], nonEmptySearchQuery),
...fuzzySearchFilters([student.fname], nonEmptySearchQuery, { distance: 2 }),
...fuzzySearchFilters([student.lname], nonEmptySearchQuery, { distance: 2 }),
...fuzzySearchFilters([student.fname, student.lname], nonEmptySearchQuery, { distance: 3 }),
...fuzzySearchFilters([student.lname, student.fname], nonEmptySearchQuery, { distance: 3 }),
]
: [])
)
Expand Down

0 comments on commit f14aa1c

Please sign in to comment.