feat(search): Enhance search functionality with robust Lucene query transformation and reliable fallback mechanisms
This commit is contained in:
@@ -84,6 +84,15 @@ export function unI() {
|
||||
}
|
||||
|
||||
export const convertFilterForMongoDb = (filterArg: { [key: string]: any }) => {
|
||||
// Special case: detect MongoDB operators and pass them through directly
|
||||
const topLevelOperators = ['$and', '$or', '$nor', '$not', '$text', '$where', '$regex'];
|
||||
for (const key of Object.keys(filterArg)) {
|
||||
if (topLevelOperators.includes(key)) {
|
||||
return filterArg; // Return the filter as-is for MongoDB operators
|
||||
}
|
||||
}
|
||||
|
||||
// Original conversion logic for non-MongoDB query objects
|
||||
const convertedFilter: { [key: string]: any } = {};
|
||||
|
||||
const convertFilterArgument = (keyPathArg2: string, filterArg2: any) => {
|
||||
@@ -272,6 +281,91 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
||||
return await (this as any).getInstances(filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search documents using Lucene query syntax with robust error handling
|
||||
* @param luceneQuery The Lucene query string to search with
|
||||
* @returns Array of matching documents
|
||||
*/
|
||||
public static async searchWithLucene<T>(
|
||||
this: plugins.tsclass.typeFest.Class<T>,
|
||||
luceneQuery: string
|
||||
): Promise<T[]> {
|
||||
try {
|
||||
const className = (this as any).className || this.name;
|
||||
const searchableFields = getSearchableFields(className);
|
||||
|
||||
if (searchableFields.length === 0) {
|
||||
console.warn(`No searchable fields defined for class ${className}, falling back to simple search`);
|
||||
return (this as any).searchByTextAcrossFields(luceneQuery);
|
||||
}
|
||||
|
||||
// Simple term search optimization
|
||||
if (!luceneQuery.includes(':') &&
|
||||
!luceneQuery.includes(' AND ') &&
|
||||
!luceneQuery.includes(' OR ') &&
|
||||
!luceneQuery.includes(' NOT ')) {
|
||||
return (this as any).searchByTextAcrossFields(luceneQuery);
|
||||
}
|
||||
|
||||
// Try to use the Lucene-to-MongoDB conversion
|
||||
const filter = (this as any).createSearchFilter(luceneQuery);
|
||||
return await (this as any).getInstances(filter);
|
||||
} catch (error) {
|
||||
console.error(`Error in searchWithLucene: ${error.message}`);
|
||||
return (this as any).searchByTextAcrossFields(luceneQuery);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search by text across all searchable fields (fallback method)
|
||||
* @param searchText The text to search for in all searchable fields
|
||||
* @returns Array of matching documents
|
||||
*/
|
||||
private static async searchByTextAcrossFields<T>(
|
||||
this: plugins.tsclass.typeFest.Class<T>,
|
||||
searchText: string
|
||||
): Promise<T[]> {
|
||||
try {
|
||||
const className = (this as any).className || this.name;
|
||||
const searchableFields = getSearchableFields(className);
|
||||
|
||||
// Fallback to direct filter if we have searchable fields
|
||||
if (searchableFields.length > 0) {
|
||||
// Create a simple $or query with regex for each field
|
||||
const orConditions = searchableFields.map(field => ({
|
||||
[field]: { $regex: searchText, $options: 'i' }
|
||||
}));
|
||||
|
||||
const filter = { $or: orConditions };
|
||||
|
||||
try {
|
||||
// Try with MongoDB filter first
|
||||
return await (this as any).getInstances(filter);
|
||||
} catch (error) {
|
||||
console.warn('MongoDB filter failed, falling back to in-memory search');
|
||||
}
|
||||
}
|
||||
|
||||
// Last resort: get all and filter in memory
|
||||
const allDocs = await (this as any).getInstances({});
|
||||
const lowerSearchText = searchText.toLowerCase();
|
||||
|
||||
return allDocs.filter((doc: any) => {
|
||||
for (const field of searchableFields) {
|
||||
const value = doc[field];
|
||||
if (value && typeof value === 'string' &&
|
||||
value.toLowerCase().includes(lowerSearchText)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error in searchByTextAcrossFields: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user