From 9c6d6d9f2ca10a7959033a815be65176024af303 Mon Sep 17 00:00:00 2001 From: Philipp Kunz Date: Tue, 22 Apr 2025 20:09:21 +0000 Subject: [PATCH] fix(search): Fix handling of quoted wildcard patterns in field-specific search queries and add tests for location-based wildcard phrase searches --- changelog.md | 6 ++++++ test/test.search.ts | 33 +++++++++++++++++++++++++++++++++ ts/00_commitinfo_data.ts | 2 +- ts/classes.doc.ts | 10 ++++++++-- 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index 6ab81b5..8dd5498 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## 2025-04-22 - 5.12.2 - fix(search) +Fix handling of quoted wildcard patterns in field-specific search queries and add tests for location-based wildcard phrase searches + +- Strip surrounding quotes from wildcard patterns in field queries to correctly transform them to regex +- Introduce new tests in test/test.search.ts to validate exact quoted and unquoted wildcard searches on a location field + ## 2025-04-22 - 5.12.1 - fix(search) Improve implicit AND logic for mixed free term and field queries in search and enhance wildcard field handling. diff --git a/test/test.search.ts b/test/test.search.ts index a186341..1401e29 100644 --- a/test/test.search.ts +++ b/test/test.search.ts @@ -9,6 +9,8 @@ import { searchable } from '../ts/classes.doc.js'; // Set up database connection let smartmongoInstance: smartmongo.SmartMongo; let testDb: smartdata.SmartdataDb; +// Class for location-based wildcard/phrase tests +let LocationDoc: any; // Define a test class with searchable fields using the standard SmartDataDbDoc @smartdata.Collection(() => testDb) @@ -290,6 +292,37 @@ tap.test('should apply validate hook to post-filter results', async () => { expensive.forEach((p) => expect(p.price).toBeGreaterThan(500)); }); +// Tests for quoted and wildcard field-specific phrases +tap.test('setup location test products', async () => { + @smartdata.Collection(() => testDb) + class LD extends smartdata.SmartDataDbDoc { + @smartdata.unI() public id: string = smartunique.shortId(); + @smartdata.svDb() @searchable() public location: string; + constructor(loc: string) { super(); this.location = loc; } + } + // Assign to outer variable for subsequent tests + LocationDoc = LD; + const locations = ['Berlin', 'Frankfurt am Main', 'Frankfurt am Oder', 'London']; + for (const loc of locations) { + await new LocationDoc(loc).save(); + } +}); +tap.test('should search exact quoted field phrase', async () => { + const results = await (LocationDoc as any).search('location:"Frankfurt am Main"'); + expect(results.length).toEqual(1); + expect(results[0].location).toEqual('Frankfurt am Main'); +}); +tap.test('should search wildcard quoted field phrase', async () => { + const results = await (LocationDoc as any).search('location:"Frankfurt am *"'); + const names = results.map((d: any) => d.location).sort(); + expect(names).toEqual(['Frankfurt am Main', 'Frankfurt am Oder']); +}); +tap.test('should search unquoted wildcard field', async () => { + const results = await (LocationDoc as any).search('location:Frankfurt*'); + const names = results.map((d: any) => d.location).sort(); + expect(names).toEqual(['Frankfurt am Main', 'Frankfurt am Oder']); +}); + // Combined free-term and field wildcard tests tap.test('should combine free term and wildcard field search', async () => { const results = await Product.search('book category:Book*'); diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 6100604..ce625fc 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartdata', - version: '5.12.1', + version: '5.12.2', description: 'An advanced library for NoSQL data organization and manipulation using TypeScript with support for MongoDB, data validation, collections, and custom data types.' } diff --git a/ts/classes.doc.ts b/ts/classes.doc.ts index ebcfde3..886dfeb 100644 --- a/ts/classes.doc.ts +++ b/ts/classes.doc.ts @@ -397,7 +397,12 @@ export class SmartDataDbDoc 1 && hasColon && !q.includes(' AND ') && !q.includes(' OR ') && !q.includes(' NOT ') && !q.includes('(') && !q.includes(')') && - !q.includes('[') && !q.includes(']') + !q.includes('[') && !q.includes(']') && + !q.includes('"') && !q.includes("'") ) { const andConds = parts.map((term) => { const m = term.match(/^(\w+):(.+)$/);