getNewMessages() and getMessagesSince() loaded all rows after a checkpoint with no cap, causing growing memory and token costs. Both queries now use a DESC LIMIT subquery to return only the most recent N messages, re-sorted chronologically. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -391,6 +391,64 @@ describe('task CRUD', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- LIMIT behavior ---
|
||||||
|
|
||||||
|
describe('message query LIMIT', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
storeChatMetadata('group@g.us', '2024-01-01T00:00:00.000Z');
|
||||||
|
|
||||||
|
for (let i = 1; i <= 10; i++) {
|
||||||
|
store({
|
||||||
|
id: `lim-${i}`,
|
||||||
|
chat_jid: 'group@g.us',
|
||||||
|
sender: 'user@s.whatsapp.net',
|
||||||
|
sender_name: 'User',
|
||||||
|
content: `message ${i}`,
|
||||||
|
timestamp: `2024-01-01T00:00:${String(i).padStart(2, '0')}.000Z`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getNewMessages caps to limit and returns most recent in chronological order', () => {
|
||||||
|
const { messages, newTimestamp } = getNewMessages(
|
||||||
|
['group@g.us'],
|
||||||
|
'2024-01-01T00:00:00.000Z',
|
||||||
|
'Andy',
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
expect(messages).toHaveLength(3);
|
||||||
|
expect(messages[0].content).toBe('message 8');
|
||||||
|
expect(messages[2].content).toBe('message 10');
|
||||||
|
// Chronological order preserved
|
||||||
|
expect(messages[1].timestamp > messages[0].timestamp).toBe(true);
|
||||||
|
// newTimestamp reflects latest returned row
|
||||||
|
expect(newTimestamp).toBe('2024-01-01T00:00:10.000Z');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getMessagesSince caps to limit and returns most recent in chronological order', () => {
|
||||||
|
const messages = getMessagesSince(
|
||||||
|
'group@g.us',
|
||||||
|
'2024-01-01T00:00:00.000Z',
|
||||||
|
'Andy',
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
expect(messages).toHaveLength(3);
|
||||||
|
expect(messages[0].content).toBe('message 8');
|
||||||
|
expect(messages[2].content).toBe('message 10');
|
||||||
|
expect(messages[1].timestamp > messages[0].timestamp).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns all messages when count is under the limit', () => {
|
||||||
|
const { messages } = getNewMessages(
|
||||||
|
['group@g.us'],
|
||||||
|
'2024-01-01T00:00:00.000Z',
|
||||||
|
'Andy',
|
||||||
|
50,
|
||||||
|
);
|
||||||
|
expect(messages).toHaveLength(10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// --- RegisteredGroup isMain round-trip ---
|
// --- RegisteredGroup isMain round-trip ---
|
||||||
|
|
||||||
describe('registered group isMain', () => {
|
describe('registered group isMain', () => {
|
||||||
|
|||||||
38
src/db.ts
38
src/db.ts
@@ -306,24 +306,29 @@ export function getNewMessages(
|
|||||||
jids: string[],
|
jids: string[],
|
||||||
lastTimestamp: string,
|
lastTimestamp: string,
|
||||||
botPrefix: string,
|
botPrefix: string,
|
||||||
|
limit: number = 200,
|
||||||
): { messages: NewMessage[]; newTimestamp: string } {
|
): { messages: NewMessage[]; newTimestamp: string } {
|
||||||
if (jids.length === 0) return { messages: [], newTimestamp: lastTimestamp };
|
if (jids.length === 0) return { messages: [], newTimestamp: lastTimestamp };
|
||||||
|
|
||||||
const placeholders = jids.map(() => '?').join(',');
|
const placeholders = jids.map(() => '?').join(',');
|
||||||
// Filter bot messages using both the is_bot_message flag AND the content
|
// Filter bot messages using both the is_bot_message flag AND the content
|
||||||
// prefix as a backstop for messages written before the migration ran.
|
// prefix as a backstop for messages written before the migration ran.
|
||||||
|
// Subquery takes the N most recent, outer query re-sorts chronologically.
|
||||||
const sql = `
|
const sql = `
|
||||||
SELECT id, chat_jid, sender, sender_name, content, timestamp, is_from_me
|
SELECT * FROM (
|
||||||
FROM messages
|
SELECT id, chat_jid, sender, sender_name, content, timestamp, is_from_me
|
||||||
WHERE timestamp > ? AND chat_jid IN (${placeholders})
|
FROM messages
|
||||||
AND is_bot_message = 0 AND content NOT LIKE ?
|
WHERE timestamp > ? AND chat_jid IN (${placeholders})
|
||||||
AND content != '' AND content IS NOT NULL
|
AND is_bot_message = 0 AND content NOT LIKE ?
|
||||||
ORDER BY timestamp
|
AND content != '' AND content IS NOT NULL
|
||||||
|
ORDER BY timestamp DESC
|
||||||
|
LIMIT ?
|
||||||
|
) ORDER BY timestamp
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const rows = db
|
const rows = db
|
||||||
.prepare(sql)
|
.prepare(sql)
|
||||||
.all(lastTimestamp, ...jids, `${botPrefix}:%`) as NewMessage[];
|
.all(lastTimestamp, ...jids, `${botPrefix}:%`, limit) as NewMessage[];
|
||||||
|
|
||||||
let newTimestamp = lastTimestamp;
|
let newTimestamp = lastTimestamp;
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
@@ -337,20 +342,25 @@ export function getMessagesSince(
|
|||||||
chatJid: string,
|
chatJid: string,
|
||||||
sinceTimestamp: string,
|
sinceTimestamp: string,
|
||||||
botPrefix: string,
|
botPrefix: string,
|
||||||
|
limit: number = 200,
|
||||||
): NewMessage[] {
|
): NewMessage[] {
|
||||||
// Filter bot messages using both the is_bot_message flag AND the content
|
// Filter bot messages using both the is_bot_message flag AND the content
|
||||||
// prefix as a backstop for messages written before the migration ran.
|
// prefix as a backstop for messages written before the migration ran.
|
||||||
|
// Subquery takes the N most recent, outer query re-sorts chronologically.
|
||||||
const sql = `
|
const sql = `
|
||||||
SELECT id, chat_jid, sender, sender_name, content, timestamp, is_from_me
|
SELECT * FROM (
|
||||||
FROM messages
|
SELECT id, chat_jid, sender, sender_name, content, timestamp, is_from_me
|
||||||
WHERE chat_jid = ? AND timestamp > ?
|
FROM messages
|
||||||
AND is_bot_message = 0 AND content NOT LIKE ?
|
WHERE chat_jid = ? AND timestamp > ?
|
||||||
AND content != '' AND content IS NOT NULL
|
AND is_bot_message = 0 AND content NOT LIKE ?
|
||||||
ORDER BY timestamp
|
AND content != '' AND content IS NOT NULL
|
||||||
|
ORDER BY timestamp DESC
|
||||||
|
LIMIT ?
|
||||||
|
) ORDER BY timestamp
|
||||||
`;
|
`;
|
||||||
return db
|
return db
|
||||||
.prepare(sql)
|
.prepare(sql)
|
||||||
.all(chatJid, sinceTimestamp, `${botPrefix}:%`) as NewMessage[];
|
.all(chatJid, sinceTimestamp, `${botPrefix}:%`, limit) as NewMessage[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTask(
|
export function createTask(
|
||||||
|
|||||||
Reference in New Issue
Block a user