Ver código fonte

fix: use inbound import lookup index

AI-Co-Authored-By: Codex
chendeben 1 mês atrás
pai
commit
3dea0649bc
2 arquivos alterados com 36 adições e 15 exclusões
  1. 19 14
      src/db.js
  2. 17 1
      test/vesta-import-db.test.js

+ 19 - 14
src/db.js

@@ -1291,18 +1291,27 @@ export function createInboundMessage(mailbox, message = {}) {
   return getInboundMessage(mailbox.userId, result.lastInsertRowid);
 }
 
+export const importedInboundMessageLookupSql = `
+  SELECT id
+  FROM inbound_messages
+  WHERE import_source = ?
+    AND import_source_key = ?
+    AND import_source != ''
+    AND import_source_key != ''
+  LIMIT 1
+`;
+
+function findImportedInboundMessage(importSource, sourceKey) {
+  return requireDb()
+    .prepare(importedInboundMessageLookupSql)
+    .get(importSource, sourceKey);
+}
+
 export function hasImportedInboundMessage(importSource, sourceKey) {
   const source = normalizeImportSource(importSource);
   const key = normalizeImportSourceKey(sourceKey);
   if (!source || !key) return false;
-  return Boolean(requireDb()
-    .prepare(`
-      SELECT 1
-      FROM inbound_messages
-      WHERE import_source = ? AND import_source_key = ?
-      LIMIT 1
-    `)
-    .get(source, key));
+  return Boolean(findImportedInboundMessage(source, key));
 }
 
 export function createImportedInboundMessage(mailbox, message = {}) {
@@ -1310,9 +1319,7 @@ export function createImportedInboundMessage(mailbox, message = {}) {
   const importSource = normalizeImportSource(message.importSource);
   const sourceKey = normalizeImportSourceKey(message.sourceKey);
   if (!importSource || !sourceKey) throw new Error('导入邮件缺少稳定来源标识。');
-  const existing = requireDb()
-    .prepare('SELECT id FROM inbound_messages WHERE import_source = ? AND import_source_key = ? LIMIT 1')
-    .get(importSource, sourceKey);
+  const existing = findImportedInboundMessage(importSource, sourceKey);
   if (existing) return { created: false, message: { id: Number(existing.id) } };
 
   const receivedAt = normalizeImportedReceivedAt(message.receivedAt);
@@ -1362,9 +1369,7 @@ export function createImportedInboundMessage(mailbox, message = {}) {
   if (result.changes) {
     return { created: true, message: { id: Number(result.lastInsertRowid) } };
   }
-  const concurrent = requireDb()
-    .prepare('SELECT id FROM inbound_messages WHERE import_source = ? AND import_source_key = ? LIMIT 1')
-    .get(importSource, sourceKey);
+  const concurrent = findImportedInboundMessage(importSource, sourceKey);
   if (!concurrent) throw new Error('导入邮件写入失败。');
   return { created: false, message: { id: Number(concurrent.id) } };
 }

+ 17 - 1
test/vesta-import-db.test.js

@@ -1,5 +1,6 @@
 import assert from 'node:assert/strict';
 import { mkdtempSync } from 'node:fs';
+import { DatabaseSync } from 'node:sqlite';
 import os from 'node:os';
 import path from 'node:path';
 import { test } from 'node:test';
@@ -10,6 +11,7 @@ import {
   getInboundMailboxProtocolMessage,
   getInboundMessage,
   hasImportedInboundMessage,
+  importedInboundMessageLookupSql,
   initDatabase,
   listInboundMailboxProtocolMessages,
   searchInboundMessages,
@@ -70,7 +72,8 @@ test('imports Vesta mailbox hashes without plaintext and upgrades after successf
 });
 
 test('imports raw Maildir bytes, flags, timestamps and skips the same source key', () => {
-  initDatabase(tempDataDir(), 'import-test-secret');
+  const dataDir = tempDataDir();
+  initDatabase(dataDir, 'import-test-secret');
   const user = seedAdminUser({ username: 'admin', email: 'admin@example.test', password: 'admin-password' });
   createTestDomain(user.id, 'mail.example');
   const mailbox = upsertImportedInboundMailbox(user.id, {
@@ -100,6 +103,19 @@ test('imports raw Maildir bytes, flags, timestamps and skips the same source key
   assert.equal(second.message.id, first.message.id);
   assert.equal(hasImportedInboundMessage(payload.importSource, payload.sourceKey), true);
 
+  const database = new DatabaseSync(path.join(dataDir, 'mailhub.sqlite'), { readOnly: true });
+  try {
+    const detail = database
+      .prepare(`EXPLAIN QUERY PLAN ${importedInboundMessageLookupSql}`)
+      .all(payload.importSource, payload.sourceKey)
+      .map((row) => row.detail)
+      .join('\n');
+    assert.match(detail, /SEARCH inbound_messages USING COVERING INDEX idx_inbound_messages_import_source_key/);
+    assert.doesNotMatch(detail, /\bSCAN inbound_messages\b/);
+  } finally {
+    database.close();
+  }
+
   const message = getInboundMessage(user.id, first.message.id);
   assert.equal(message.rawMessage, rawMessageBytes.toString('utf8'));
   assert.equal(message.read, true);