diff --git a/workers/durableObject/index.ts b/workers/durableObject/index.ts index e2cbbfa3..e584d32c 100644 --- a/workers/durableObject/index.ts +++ b/workers/durableObject/index.ts @@ -35,6 +35,21 @@ const ALLOWED_SORT_COLUMNS = [ type SortColumn = (typeof ALLOWED_SORT_COLUMNS)[number]; +const SQLITE_LIKE_PATTERN_LIMIT = 50; +const LIKE_WILDCARD_PADDING = 2; +const MAX_LIKE_TERM_LENGTH = SQLITE_LIKE_PATTERN_LIMIT - LIKE_WILDCARD_PADDING; + +function splitLikeTerm(value: string): string[] { + const trimmed = value.trim(); + if (!trimmed) return []; + + const chunks: string[] = []; + for (let i = 0; i < trimmed.length; i += MAX_LIKE_TERM_LENGTH) { + chunks.push(trimmed.slice(i, i + MAX_LIKE_TERM_LENGTH)); + } + return chunks; +} + /** * Map SortColumn string names to Drizzle column references for safe * ORDER BY construction (no string interpolation into SQL). @@ -671,20 +686,35 @@ export class MailboxDO extends DurableObject { return `?${paramIdx}`; }; + const addLikeConditions = (columns: string[], value: string) => { + for (const term of splitLikeTerm(value)) { + const columnConditions = columns.map( + (column) => `${column} LIKE ${addParam(`%${term}%`)}`, + ); + conditions.push(`(${columnConditions.join(" OR ")})`); + } + }; + if (query) { - const p1 = addParam(`%${query}%`); - const p2 = addParam(`%${query}%`); - const p3 = addParam(`%${query}%`); - const p4 = addParam(`%${query}%`); - conditions.push(`(${prefix}subject LIKE ${p1} OR ${prefix}body LIKE ${p2} OR ${prefix}sender LIKE ${p3} OR ${prefix}recipient LIKE ${p4} OR ${prefix}cc LIKE ${p4} OR ${prefix}bcc LIKE ${p4})`); + addLikeConditions( + [ + `${prefix}subject`, + `${prefix}body`, + `${prefix}sender`, + `${prefix}recipient`, + `${prefix}cc`, + `${prefix}bcc`, + ], + query, + ); } if (folder) { const p = addParam(folder); conditions.push(`${prefix}folder_id = (SELECT id FROM folders WHERE name = ${p} OR id = ${p} LIMIT 1)`); } - if (from) { const p = addParam(`%${from}%`); conditions.push(`${prefix}sender LIKE ${p}`); } - if (to) { const p = addParam(`%${to}%`); conditions.push(`(${prefix}recipient LIKE ${p} OR ${prefix}cc LIKE ${p} OR ${prefix}bcc LIKE ${p})`); } - if (subject) { const p = addParam(`%${subject}%`); conditions.push(`${prefix}subject LIKE ${p}`); } + if (from) addLikeConditions([`${prefix}sender`], from); + if (to) addLikeConditions([`${prefix}recipient`, `${prefix}cc`, `${prefix}bcc`], to); + if (subject) addLikeConditions([`${prefix}subject`], subject); if (date_start) { const p = addParam(date_start); conditions.push(`${prefix}date >= ${p}`); } if (date_end) { const p = addParam(date_end); conditions.push(`${prefix}date <= ${p}`); } if (is_read !== undefined) { const p = addParam(is_read ? 1 : 0); conditions.push(`${prefix}read = ${p}`); }