mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-05-07 05:32:45 +08:00
D1 caps LIKE/GLOB pattern length at 50 bytes. /admin/address and
/admin/users wrapped the query as `%${query}%` and fed it to LIKE,
so searching by a full email address crashed with "LIKE or GLOB
pattern too complex". Fall back to instr() above the 50-byte
threshold.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,8 @@
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- fix: |Admin| 修复 `/admin/address` 与 `/admin/users` 在使用完整邮箱(query 长度超过 50 字节)作为搜索条件时报错 `D1_ERROR: LIKE or GLOB pattern too complex` 的问题,长查询自动改用 `instr()` 绕开 D1 的 LIKE pattern 长度限制(#956)
|
||||
|
||||
### Improvements
|
||||
|
||||
- docs: |发送邮件 API| 明确 `/api/send_mail` 与 `/external/api/send_mail` 两个端点的认证方式差异,补充"地址 JWT"概念说明(#922)
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- fix: |Admin| Fix `D1_ERROR: LIKE or GLOB pattern too complex` on `/admin/address` and `/admin/users` when searching by full email address (query length pushes the LIKE pattern over D1's 50-byte limit). Long queries now fall back to `instr()` to bypass the LIKE pattern length cap (#956)
|
||||
|
||||
### Improvements
|
||||
|
||||
- docs: |Send Mail API| Clarify authentication differences between `/api/send_mail` and `/external/api/send_mail`, add "Address JWT" concept explanation (#922)
|
||||
|
||||
48
e2e/tests/api/admin-address-query.spec.ts
Normal file
48
e2e/tests/api/admin-address-query.spec.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { WORKER_URL, TEST_DOMAIN, createTestAddress } from '../../fixtures/test-helpers';
|
||||
|
||||
// Regression tests for #956: long admin search queries must not trigger
|
||||
// D1's "LIKE or GLOB pattern too complex" error.
|
||||
test.describe('Admin Address Query (#956)', () => {
|
||||
test('short query (subdomain fragment) returns matching address via LIKE', async ({ request }) => {
|
||||
const created = await createTestAddress(request, 'q956short');
|
||||
const fragment = created.address.split('@')[0].slice(0, 8);
|
||||
|
||||
const res = await request.get(`${WORKER_URL}/admin/address`, {
|
||||
params: { limit: '20', offset: '0', query: fragment },
|
||||
});
|
||||
expect(res.ok()).toBe(true);
|
||||
const body = await res.json();
|
||||
expect(Array.isArray(body.results)).toBe(true);
|
||||
const names: string[] = body.results.map((r: any) => r.name);
|
||||
expect(names).toContain(created.address);
|
||||
});
|
||||
|
||||
test('long query (>50-byte pattern) does not crash with D1 LIKE error', async ({ request }) => {
|
||||
const longQuery = 'a48r893s@5hx7zb.nationalgeographic.algomindtrade.com';
|
||||
expect(new TextEncoder().encode(`%${longQuery}%`).length).toBeGreaterThan(50);
|
||||
|
||||
const res = await request.get(`${WORKER_URL}/admin/address`, {
|
||||
params: { limit: '20', offset: '0', query: longQuery },
|
||||
});
|
||||
expect(res.status()).toBe(200);
|
||||
const body = await res.json();
|
||||
expect(Array.isArray(body.results)).toBe(true);
|
||||
expect(body.results.length).toBe(0);
|
||||
expect(body.count).toBe(0);
|
||||
});
|
||||
|
||||
test('long query also works for /admin/users', async ({ request }) => {
|
||||
const longQuery = 'no-such-user-' + 'x'.repeat(40) + `@${TEST_DOMAIN}`;
|
||||
expect(new TextEncoder().encode(`%${longQuery}%`).length).toBeGreaterThan(50);
|
||||
|
||||
const res = await request.get(`${WORKER_URL}/admin/users`, {
|
||||
params: { limit: '20', offset: '0', query: longQuery },
|
||||
});
|
||||
expect(res.status()).toBe(200);
|
||||
const body = await res.json();
|
||||
expect(Array.isArray(body.results)).toBe(true);
|
||||
expect(body.results.length).toBe(0);
|
||||
expect(body.count).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -39,15 +39,21 @@ export default {
|
||||
getUsers: async (c: Context<HonoCustomType>) => {
|
||||
const { limit, offset, query } = c.req.query();
|
||||
if (query) {
|
||||
// D1 caps LIKE pattern length at 50 bytes; fall back to instr()
|
||||
// for longer queries to avoid "LIKE or GLOB pattern too complex" (#956).
|
||||
const useInstr = new TextEncoder().encode(query).length + 2 > 50;
|
||||
const param = useInstr ? query : `%${query}%`;
|
||||
const userEmailWhere = useInstr ? `instr(u.user_email, ?) > 0` : `u.user_email like ?`;
|
||||
const userEmailWhereCount = useInstr ? `instr(user_email, ?) > 0` : `user_email like ?`;
|
||||
return await handleListQuery(c,
|
||||
`SELECT u.id as id, u.user_email, u.created_at, u.updated_at,`
|
||||
+ ` ur.role_text as role_text,`
|
||||
+ ` (SELECT COUNT(*) FROM users_address WHERE user_id = u.id) AS address_count`
|
||||
+ ` FROM users u`
|
||||
+ ` LEFT JOIN user_roles ur ON u.id = ur.user_id`
|
||||
+ ` where u.user_email like ?`,
|
||||
`SELECT count(*) as count FROM users where user_email like ?`,
|
||||
[`%${query}%`], limit, offset
|
||||
+ ` where ${userEmailWhere}`,
|
||||
`SELECT count(*) as count FROM users where ${userEmailWhereCount}`,
|
||||
[param], limit, offset
|
||||
);
|
||||
}
|
||||
return await handleListQuery(c,
|
||||
|
||||
@@ -76,14 +76,19 @@ api.get('/admin/address', async (c) => {
|
||||
const sortDirection = sort_order === 'ascend' ? 'asc' : 'desc';
|
||||
const orderBy = `${sortColumn} ${sortDirection}`;
|
||||
if (query) {
|
||||
// D1 caps LIKE pattern length at 50 bytes; fall back to instr() for
|
||||
// longer queries to avoid "LIKE or GLOB pattern too complex" (#956).
|
||||
const useInstr = new TextEncoder().encode(query).length + 2 > 50;
|
||||
const whereClause = useInstr ? `instr(name, ?) > 0` : `name like ?`;
|
||||
const param = useInstr ? query : `%${query}%`;
|
||||
return await handleListQuery(c,
|
||||
`SELECT a.*,`
|
||||
+ ` (SELECT COUNT(*) FROM raw_mails WHERE address = a.name) AS mail_count,`
|
||||
+ ` (SELECT COUNT(*) FROM sendbox WHERE address = a.name) AS send_count`
|
||||
+ ` FROM address a`
|
||||
+ ` where name like ?`,
|
||||
`SELECT count(*) as count FROM address where name like ?`,
|
||||
[`%${query}%`], limit, offset, orderBy
|
||||
+ ` where ${whereClause}`,
|
||||
`SELECT count(*) as count FROM address where ${whereClause}`,
|
||||
[param], limit, offset, orderBy
|
||||
);
|
||||
}
|
||||
return await handleListQuery(c,
|
||||
|
||||
Reference in New Issue
Block a user