@remit/drizzle-service 0.0.104 → 0.0.105
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/bound-parameters.test.ts +57 -0
- package/src/bound-parameters.ts +49 -0
- package/src/databases.ts +10 -9
- package/src/db.ts +1 -0
- package/src/index.ts +4 -0
- package/src/repair/junk-only-address.sqlite.test.ts +9 -5
- package/src/repos/account.conformance.sqlite.test.ts +9 -5
- package/src/repos/calendar-event-index.conformance.d1.test.ts +1 -1
- package/src/repos/calendar-event-index.conformance.sqlite.test.ts +3 -1
- package/src/repos/calendar-event-index.ts +36 -21
- package/src/repos/calendar-store.ts +17 -10
- package/src/repos/cascade-delete.test.ts +9 -2
- package/src/repos/cascade-delete.ts +2 -1
- package/src/repos/envelope.d1.test.ts +66 -0
- package/src/repos/envelope.sqlite.test.ts +5 -1
- package/src/repos/envelope.test.ts +5 -1
- package/src/repos/envelope.ts +40 -31
- package/src/repos/i4-account.test.ts +17 -10
- package/src/repos/i4-address-junk-move.sqlite.test.ts +9 -5
- package/src/repos/i4-mailbox-special-use.sqlite.test.ts +9 -5
- package/src/repos/i4-mailbox.sqlite.test.ts +17 -10
- package/src/repos/i4-mailbox.test.ts +17 -10
- package/src/repos/i4-outbox-attachment.test.ts +9 -5
- package/src/repos/message.sqlite.test.ts +5 -1
- package/src/repos/message.test.ts +5 -1
- package/src/repos/sync-warning.ts +35 -22
- package/src/repos/thread-message-body-muted.sqlite.test.ts +9 -5
- package/src/repos/thread-message-field-terms.sqlite.test.ts +9 -5
- package/src/repos/thread-message.sqlite.test.ts +9 -5
- package/src/repos/thread-message.test.ts +33 -20
- package/src/search/thread-message-search.sqlite.test.ts +13 -6
- package/src/search/thread-message-search.ts +14 -17
- package/src/sqlite-repositories.ts +6 -3
- package/src/test-d1.ts +36 -19
- package/src/test-stores.ts +15 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { after, before, describe, test } from "node:test";
|
|
3
|
+
import { BETTER_SQLITE3_LIMITS } from "../bound-parameters.js";
|
|
3
4
|
import { createSqliteRepositories } from "../sqlite-repositories.js";
|
|
4
5
|
import { createTestDb, randomId, type TestDb } from "../test-db.js";
|
|
5
6
|
import type { AccountRepo } from "./i4-account.js";
|
|
@@ -26,11 +27,14 @@ describe("AccountRepo", () => {
|
|
|
26
27
|
|
|
27
28
|
before(async () => {
|
|
28
29
|
({ db, close } = await createTestDb());
|
|
29
|
-
repo = createSqliteRepositories(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
repo = createSqliteRepositories(
|
|
31
|
+
{
|
|
32
|
+
app: db,
|
|
33
|
+
mail: db,
|
|
34
|
+
search: db,
|
|
35
|
+
},
|
|
36
|
+
BETTER_SQLITE3_LIMITS,
|
|
37
|
+
).account;
|
|
34
38
|
});
|
|
35
39
|
|
|
36
40
|
after(async () => {
|
|
@@ -164,11 +168,14 @@ describe("AccountRepo", () => {
|
|
|
164
168
|
const accountConfigId = randomId();
|
|
165
169
|
const account = await repo.create(makeAccountInput(accountConfigId));
|
|
166
170
|
|
|
167
|
-
const mailboxRepo = createSqliteRepositories(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
171
|
+
const mailboxRepo = createSqliteRepositories(
|
|
172
|
+
{
|
|
173
|
+
app: db,
|
|
174
|
+
mail: db,
|
|
175
|
+
search: db,
|
|
176
|
+
},
|
|
177
|
+
BETTER_SQLITE3_LIMITS,
|
|
178
|
+
).mailbox;
|
|
172
179
|
mailboxCreated(
|
|
173
180
|
await mailboxRepo.create({
|
|
174
181
|
accountId: account.accountId,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { after, before, beforeEach, describe, test } from "node:test";
|
|
3
3
|
import type Database from "better-sqlite3";
|
|
4
|
+
import { BETTER_SQLITE3_LIMITS } from "../bound-parameters.js";
|
|
4
5
|
import { createSqliteRepositories } from "../sqlite-repositories.js";
|
|
5
6
|
import { createTestDb, type TestDb } from "../test-db.js";
|
|
6
7
|
import { AddressRepo } from "./i4-address.js";
|
|
@@ -125,11 +126,14 @@ describe("reconciling one message's addresses at the moment it moves", () => {
|
|
|
125
126
|
before(async () => {
|
|
126
127
|
({ db, sqlite, close } = await createTestDb());
|
|
127
128
|
repo = new AddressRepo(db as never);
|
|
128
|
-
specialUse = createSqliteRepositories(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
specialUse = createSqliteRepositories(
|
|
130
|
+
{
|
|
131
|
+
app: db,
|
|
132
|
+
mail: db,
|
|
133
|
+
search: db,
|
|
134
|
+
},
|
|
135
|
+
BETTER_SQLITE3_LIMITS,
|
|
136
|
+
).mailboxSpecialUse;
|
|
133
137
|
});
|
|
134
138
|
|
|
135
139
|
/**
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
meetsTrashAssurance,
|
|
7
7
|
} from "@remit/data-ports/folder-role";
|
|
8
8
|
import { CanonicalMailboxRole } from "@remit/domain-enums";
|
|
9
|
+
import { BETTER_SQLITE3_LIMITS } from "../bound-parameters.js";
|
|
9
10
|
import {
|
|
10
11
|
accountSettingTable,
|
|
11
12
|
accountTable,
|
|
@@ -57,11 +58,14 @@ describe("MailboxSpecialUseRepo role lookups (sqlite)", () => {
|
|
|
57
58
|
});
|
|
58
59
|
close = testDb.close;
|
|
59
60
|
db = testDb.db;
|
|
60
|
-
const repos = createSqliteRepositories(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
const repos = createSqliteRepositories(
|
|
62
|
+
{
|
|
63
|
+
app: testDb.db,
|
|
64
|
+
mail: testDb.db,
|
|
65
|
+
search: testDb.db,
|
|
66
|
+
},
|
|
67
|
+
BETTER_SQLITE3_LIMITS,
|
|
68
|
+
);
|
|
65
69
|
repo = repos.mailboxSpecialUse;
|
|
66
70
|
mailboxes = repos.mailbox;
|
|
67
71
|
accountSettings = repos.accountSetting;
|
|
@@ -4,6 +4,7 @@ import { after, before, describe, test } from "node:test";
|
|
|
4
4
|
import { MailboxSyncStatus } from "@remit/domain-enums";
|
|
5
5
|
import Database from "better-sqlite3";
|
|
6
6
|
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
7
|
+
import { BETTER_SQLITE3_LIMITS } from "../bound-parameters.js";
|
|
7
8
|
import { mailboxTable } from "../schema.js";
|
|
8
9
|
import { createSqliteRepositories } from "../sqlite-repositories.js";
|
|
9
10
|
import { createSqliteTestDb } from "../test-db-sqlite.js";
|
|
@@ -41,11 +42,14 @@ describe("MailboxRepo (sqlite)", () => {
|
|
|
41
42
|
before(async () => {
|
|
42
43
|
const testDb = await createSqliteTestDb({ mailbox: mailboxTable });
|
|
43
44
|
close = testDb.close;
|
|
44
|
-
repo = createSqliteRepositories(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
repo = createSqliteRepositories(
|
|
46
|
+
{
|
|
47
|
+
app: testDb.db,
|
|
48
|
+
mail: testDb.db,
|
|
49
|
+
search: testDb.db,
|
|
50
|
+
},
|
|
51
|
+
BETTER_SQLITE3_LIMITS,
|
|
52
|
+
).mailbox;
|
|
49
53
|
});
|
|
50
54
|
|
|
51
55
|
after(async () => {
|
|
@@ -211,11 +215,14 @@ describe("MailboxRepo (sqlite, shipped migrations)", () => {
|
|
|
211
215
|
const sqlite = new Database(":memory:");
|
|
212
216
|
applyMigrationSet(sqlite, "mail");
|
|
213
217
|
const db = drizzle(sqlite, { schema: { mailbox: mailboxTable } });
|
|
214
|
-
repo = createSqliteRepositories(
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
218
|
+
repo = createSqliteRepositories(
|
|
219
|
+
{
|
|
220
|
+
app: db,
|
|
221
|
+
mail: db,
|
|
222
|
+
search: db,
|
|
223
|
+
},
|
|
224
|
+
BETTER_SQLITE3_LIMITS,
|
|
225
|
+
).mailbox;
|
|
219
226
|
close = async () => {
|
|
220
227
|
sqlite.close();
|
|
221
228
|
};
|
|
@@ -3,6 +3,7 @@ import { after, before, describe, test } from "node:test";
|
|
|
3
3
|
import { MailboxSyncStatus } from "@remit/domain-enums";
|
|
4
4
|
import { eq } from "drizzle-orm";
|
|
5
5
|
import { syncBatch } from "../batch.js";
|
|
6
|
+
import { BETTER_SQLITE3_LIMITS } from "../bound-parameters.js";
|
|
6
7
|
import {
|
|
7
8
|
envelopeId as deriveEnvelopeId,
|
|
8
9
|
rootBodyPartId as deriveRootBodyPartId,
|
|
@@ -54,11 +55,14 @@ describe("MailboxRepo", () => {
|
|
|
54
55
|
|
|
55
56
|
before(async () => {
|
|
56
57
|
({ db, close } = await createTestDb());
|
|
57
|
-
repo = createSqliteRepositories(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
repo = createSqliteRepositories(
|
|
59
|
+
{
|
|
60
|
+
app: db,
|
|
61
|
+
mail: db,
|
|
62
|
+
search: db,
|
|
63
|
+
},
|
|
64
|
+
BETTER_SQLITE3_LIMITS,
|
|
65
|
+
).mailbox;
|
|
62
66
|
});
|
|
63
67
|
|
|
64
68
|
after(async () => {
|
|
@@ -543,11 +547,14 @@ describe("MailboxRepo", () => {
|
|
|
543
547
|
describe("deleteMailboxWithMail — the folder's mail goes with it (D8)", () => {
|
|
544
548
|
const seedMessage = async (mailboxId: string, uid: number) => {
|
|
545
549
|
const messageId = randomId();
|
|
546
|
-
await createSqliteRepositories(
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
550
|
+
await createSqliteRepositories(
|
|
551
|
+
{
|
|
552
|
+
app: db,
|
|
553
|
+
mail: db,
|
|
554
|
+
search: db,
|
|
555
|
+
},
|
|
556
|
+
BETTER_SQLITE3_LIMITS,
|
|
557
|
+
).message.create({
|
|
551
558
|
messageId,
|
|
552
559
|
mailboxId,
|
|
553
560
|
uid,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { after, before, describe, test } from "node:test";
|
|
3
|
+
import { BETTER_SQLITE3_LIMITS } from "../bound-parameters.js";
|
|
3
4
|
import { createSqliteRepositories } from "../sqlite-repositories.js";
|
|
4
5
|
import { createTestDb, randomId } from "../test-db.js";
|
|
5
6
|
import type { OutboxAttachmentRepo } from "./i4-outbox-attachment.js";
|
|
@@ -28,11 +29,14 @@ const input = (
|
|
|
28
29
|
|
|
29
30
|
before(async () => {
|
|
30
31
|
harness = await createTestDb();
|
|
31
|
-
repo = createSqliteRepositories(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
repo = createSqliteRepositories(
|
|
33
|
+
{
|
|
34
|
+
app: harness.db,
|
|
35
|
+
mail: harness.db,
|
|
36
|
+
search: harness.db,
|
|
37
|
+
},
|
|
38
|
+
BETTER_SQLITE3_LIMITS,
|
|
39
|
+
).outboxAttachment;
|
|
36
40
|
});
|
|
37
41
|
|
|
38
42
|
after(async () => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { after, before, describe, test } from "node:test";
|
|
3
|
+
import { BETTER_SQLITE3_LIMITS } from "../bound-parameters.js";
|
|
3
4
|
import {
|
|
4
5
|
envelopeId as deriveEnvelopeId,
|
|
5
6
|
rootBodyPartId as deriveRootBodyPartId,
|
|
@@ -43,7 +44,10 @@ describe("DrizzleMessageRepository (sqlite)", () => {
|
|
|
43
44
|
|
|
44
45
|
before(async () => {
|
|
45
46
|
({ db, close } = await createSqliteTestDb(SCHEMA));
|
|
46
|
-
repo = createSqliteRepositories(
|
|
47
|
+
repo = createSqliteRepositories(
|
|
48
|
+
{ app: db, mail: db, search: db },
|
|
49
|
+
BETTER_SQLITE3_LIMITS,
|
|
50
|
+
).message;
|
|
47
51
|
});
|
|
48
52
|
|
|
49
53
|
after(async () => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { after, before, describe, test } from "node:test";
|
|
3
|
+
import { BETTER_SQLITE3_LIMITS } from "../bound-parameters.js";
|
|
3
4
|
import {
|
|
4
5
|
envelopeId as deriveEnvelopeId,
|
|
5
6
|
rootBodyPartId as deriveRootBodyPartId,
|
|
@@ -37,7 +38,10 @@ describe("DrizzleMessageRepository", () => {
|
|
|
37
38
|
|
|
38
39
|
before(async () => {
|
|
39
40
|
({ db, stop } = await createTestDb());
|
|
40
|
-
const repos = createSqliteRepositories(
|
|
41
|
+
const repos = createSqliteRepositories(
|
|
42
|
+
{ app: db, mail: db, search: db },
|
|
43
|
+
BETTER_SQLITE3_LIMITS,
|
|
44
|
+
);
|
|
41
45
|
messageRepo = repos.message;
|
|
42
46
|
envelopeRepo = repos.envelope;
|
|
43
47
|
});
|
|
@@ -6,14 +6,24 @@ import type {
|
|
|
6
6
|
} from "@remit/data-ports";
|
|
7
7
|
import { deriveSyncWarningId } from "@remit/data-ports/id";
|
|
8
8
|
import { and, count, desc, eq, gte, sql } from "drizzle-orm";
|
|
9
|
-
import type {
|
|
9
|
+
import type { WriteBatch } from "../batch.js";
|
|
10
|
+
import { chunkRows } from "../bound-parameters.js";
|
|
11
|
+
import type { RepositoryDb, SqliteStore } from "../db.js";
|
|
10
12
|
import { syncWarningTable } from "../schema/sync-warning.js";
|
|
11
13
|
import { inList } from "./in-list.js";
|
|
12
14
|
|
|
13
15
|
type DB = RepositoryDb<Record<string, unknown>>;
|
|
14
16
|
|
|
15
17
|
export class SyncWarningRepo implements ISyncWarningRepository {
|
|
16
|
-
|
|
18
|
+
private readonly db: DB;
|
|
19
|
+
private readonly batch: WriteBatch;
|
|
20
|
+
private readonly maxBoundParameters: number;
|
|
21
|
+
|
|
22
|
+
constructor(store: SqliteStore<Record<string, unknown>>) {
|
|
23
|
+
this.db = store.db;
|
|
24
|
+
this.batch = store.batch;
|
|
25
|
+
this.maxBoundParameters = store.maxBoundParameters;
|
|
26
|
+
}
|
|
17
27
|
|
|
18
28
|
listByMailboxId = async (mailboxId: string): Promise<SyncWarningItem[]> =>
|
|
19
29
|
this.db
|
|
@@ -46,26 +56,29 @@ export class SyncWarningRepo implements ISyncWarningRepository {
|
|
|
46
56
|
recordFailures = async (keys: SyncWarningKey[]): Promise<void> => {
|
|
47
57
|
if (keys.length === 0) return;
|
|
48
58
|
const now = Date.now();
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
const rows = keys.map((key) => ({
|
|
60
|
+
syncWarningId: deriveSyncWarningId(
|
|
61
|
+
key.accountId,
|
|
62
|
+
key.mailboxId,
|
|
63
|
+
key.uidValidity,
|
|
64
|
+
key.uid,
|
|
65
|
+
key.stage,
|
|
66
|
+
),
|
|
67
|
+
...key,
|
|
68
|
+
attempts: 1,
|
|
69
|
+
createdAt: now,
|
|
70
|
+
}));
|
|
71
|
+
await this.batch(
|
|
72
|
+
chunkRows(syncWarningTable, rows, this.maxBoundParameters).map((slice) =>
|
|
73
|
+
this.db
|
|
74
|
+
.insert(syncWarningTable)
|
|
75
|
+
.values(slice)
|
|
76
|
+
.onConflictDoUpdate({
|
|
77
|
+
target: syncWarningTable.syncWarningId,
|
|
78
|
+
set: { attempts: sql`${syncWarningTable.attempts} + 1` },
|
|
79
|
+
}),
|
|
80
|
+
),
|
|
81
|
+
);
|
|
69
82
|
};
|
|
70
83
|
|
|
71
84
|
deleteMany = async (syncWarningIds: string[]): Promise<void> => {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import assert from "node:assert/strict";
|
|
11
11
|
import { after, before, describe, test } from "node:test";
|
|
12
12
|
import type { CreateThreadMessageInput } from "@remit/data-ports";
|
|
13
|
+
import { BETTER_SQLITE3_LIMITS } from "../bound-parameters.js";
|
|
13
14
|
import { addressTable } from "../schema/i4-address.js";
|
|
14
15
|
import { messageSearchTextTable } from "../schema/message-data.js";
|
|
15
16
|
import { threadMessageTable } from "../schema/thread-message.js";
|
|
@@ -60,11 +61,14 @@ describe("thread-message body and muted-sender predicates (sqlite)", () => {
|
|
|
60
61
|
},
|
|
61
62
|
{ searchIndex: true },
|
|
62
63
|
));
|
|
63
|
-
repo = createSqliteRepositories(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
repo = createSqliteRepositories(
|
|
65
|
+
{
|
|
66
|
+
app: db,
|
|
67
|
+
mail: db,
|
|
68
|
+
search: db,
|
|
69
|
+
},
|
|
70
|
+
BETTER_SQLITE3_LIMITS,
|
|
71
|
+
).threadMessage;
|
|
68
72
|
});
|
|
69
73
|
|
|
70
74
|
after(async () => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { after, before, describe, test } from "node:test";
|
|
3
3
|
import type { CreateThreadMessageInput } from "@remit/data-ports";
|
|
4
|
+
import { BETTER_SQLITE3_LIMITS } from "../bound-parameters.js";
|
|
4
5
|
import { messageSearchTextTable } from "../schema/message-data.js";
|
|
5
6
|
import { threadMessageTable } from "../schema/thread-message.js";
|
|
6
7
|
import { createSqliteRepositories } from "../sqlite-repositories.js";
|
|
@@ -59,11 +60,14 @@ describe("DrizzleThreadMessageRepository.listByFieldTerms (sqlite, #459)", () =>
|
|
|
59
60
|
{ searchIndex: true },
|
|
60
61
|
);
|
|
61
62
|
close = created.close;
|
|
62
|
-
repo = createSqliteRepositories(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
repo = createSqliteRepositories(
|
|
64
|
+
{
|
|
65
|
+
app: created.db,
|
|
66
|
+
mail: created.db,
|
|
67
|
+
search: created.db,
|
|
68
|
+
},
|
|
69
|
+
BETTER_SQLITE3_LIMITS,
|
|
70
|
+
).threadMessage;
|
|
67
71
|
|
|
68
72
|
await repo.create(
|
|
69
73
|
makeInput({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { after, before, describe, test } from "node:test";
|
|
3
3
|
import type { CreateThreadMessageInput } from "@remit/data-ports";
|
|
4
|
+
import { BETTER_SQLITE3_LIMITS } from "../bound-parameters.js";
|
|
4
5
|
import { messageSearchTextTable } from "../schema/message-data.js";
|
|
5
6
|
import { threadMessageTable } from "../schema/thread-message.js";
|
|
6
7
|
import { createSqliteRepositories } from "../sqlite-repositories.js";
|
|
@@ -60,11 +61,14 @@ describe("DrizzleThreadMessageRepository (sqlite)", () => {
|
|
|
60
61
|
},
|
|
61
62
|
{ searchIndex: true },
|
|
62
63
|
));
|
|
63
|
-
repo = createSqliteRepositories(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
repo = createSqliteRepositories(
|
|
65
|
+
{
|
|
66
|
+
app: db,
|
|
67
|
+
mail: db,
|
|
68
|
+
search: db,
|
|
69
|
+
},
|
|
70
|
+
BETTER_SQLITE3_LIMITS,
|
|
71
|
+
).threadMessage;
|
|
68
72
|
});
|
|
69
73
|
|
|
70
74
|
after(async () => {
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
IMessageRepository,
|
|
6
6
|
} from "@remit/data-ports";
|
|
7
7
|
import shortUuid from "short-uuid";
|
|
8
|
+
import { BETTER_SQLITE3_LIMITS } from "../bound-parameters.js";
|
|
8
9
|
import { messageSearchTextTable } from "../schema/message-data.js";
|
|
9
10
|
import { threadMessageTable } from "../schema/thread-message.js";
|
|
10
11
|
import { createSqliteRepositories } from "../sqlite-repositories.js";
|
|
@@ -94,11 +95,14 @@ describe("DrizzleThreadMessageRepository.searchByMailboxWindow / countByMailbox"
|
|
|
94
95
|
before(async () => {
|
|
95
96
|
const harness = await setupDb();
|
|
96
97
|
close = harness.close;
|
|
97
|
-
repo = createSqliteRepositories(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
repo = createSqliteRepositories(
|
|
99
|
+
{
|
|
100
|
+
app: harness.db,
|
|
101
|
+
mail: harness.db,
|
|
102
|
+
search: harness.db,
|
|
103
|
+
},
|
|
104
|
+
BETTER_SQLITE3_LIMITS,
|
|
105
|
+
).threadMessage;
|
|
102
106
|
});
|
|
103
107
|
|
|
104
108
|
after(async () => {
|
|
@@ -400,11 +404,14 @@ describe("DrizzleThreadMessageRepository.searchByDate", () => {
|
|
|
400
404
|
before(async () => {
|
|
401
405
|
const harness = await setupDb();
|
|
402
406
|
close = harness.close;
|
|
403
|
-
const repositories = createSqliteRepositories(
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
407
|
+
const repositories = createSqliteRepositories(
|
|
408
|
+
{
|
|
409
|
+
app: harness.db,
|
|
410
|
+
mail: harness.db,
|
|
411
|
+
search: harness.db,
|
|
412
|
+
},
|
|
413
|
+
BETTER_SQLITE3_LIMITS,
|
|
414
|
+
);
|
|
408
415
|
repo = repositories.threadMessage;
|
|
409
416
|
messages = repositories.message;
|
|
410
417
|
});
|
|
@@ -935,11 +942,14 @@ describe("DrizzleThreadMessageRepository — native text search", () => {
|
|
|
935
942
|
before(async () => {
|
|
936
943
|
const harness = await setupDb();
|
|
937
944
|
close = harness.close;
|
|
938
|
-
repo = createSqliteRepositories(
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
945
|
+
repo = createSqliteRepositories(
|
|
946
|
+
{
|
|
947
|
+
app: harness.db,
|
|
948
|
+
mail: harness.db,
|
|
949
|
+
search: harness.db,
|
|
950
|
+
},
|
|
951
|
+
BETTER_SQLITE3_LIMITS,
|
|
952
|
+
).threadMessage;
|
|
943
953
|
});
|
|
944
954
|
|
|
945
955
|
after(async () => {
|
|
@@ -1111,11 +1121,14 @@ describe("DrizzleThreadMessageRepository — core CRUD", () => {
|
|
|
1111
1121
|
before(async () => {
|
|
1112
1122
|
const harness = await setupDb();
|
|
1113
1123
|
close = harness.close;
|
|
1114
|
-
repo = createSqliteRepositories(
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1124
|
+
repo = createSqliteRepositories(
|
|
1125
|
+
{
|
|
1126
|
+
app: harness.db,
|
|
1127
|
+
mail: harness.db,
|
|
1128
|
+
search: harness.db,
|
|
1129
|
+
},
|
|
1130
|
+
BETTER_SQLITE3_LIMITS,
|
|
1131
|
+
).threadMessage;
|
|
1119
1132
|
});
|
|
1120
1133
|
|
|
1121
1134
|
after(async () => {
|
|
@@ -3,6 +3,7 @@ import { after, before, describe, test } from "node:test";
|
|
|
3
3
|
import type { CreateThreadMessageInput } from "@remit/data-ports";
|
|
4
4
|
import Database from "better-sqlite3";
|
|
5
5
|
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
6
|
+
import { BETTER_SQLITE3_LIMITS } from "../bound-parameters.js";
|
|
6
7
|
import { syncStore } from "../databases.js";
|
|
7
8
|
import type { Db } from "../db.js";
|
|
8
9
|
import { messageSearchTextTable } from "../schema/message-data.js";
|
|
@@ -50,12 +51,18 @@ const open = (set: "mail" | "search") => {
|
|
|
50
51
|
describe("the thread-message search index in its own database", () => {
|
|
51
52
|
const mail = open("mail");
|
|
52
53
|
const search = open("search");
|
|
53
|
-
const index = new SearchIndex(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
const index = new SearchIndex(
|
|
55
|
+
mail.db,
|
|
56
|
+
syncStore(search.db, BETTER_SQLITE3_LIMITS),
|
|
57
|
+
);
|
|
58
|
+
const repo = createSqliteRepositories(
|
|
59
|
+
{
|
|
60
|
+
app: mail.db,
|
|
61
|
+
mail: mail.db,
|
|
62
|
+
search: search.db,
|
|
63
|
+
},
|
|
64
|
+
BETTER_SQLITE3_LIMITS,
|
|
65
|
+
).threadMessage;
|
|
59
66
|
|
|
60
67
|
const found = async (query: string): Promise<string[]> =>
|
|
61
68
|
(await repo.searchByMailboxWindow(ACCOUNT, MAILBOX, { query })).items.map(
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "drizzle-orm";
|
|
14
14
|
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
15
15
|
import type { SqliteBatchItem, WriteBatch } from "../batch.js";
|
|
16
|
+
import { chunkRows } from "../bound-parameters.js";
|
|
16
17
|
import type { RepositoryDb, SqliteStore } from "../db.js";
|
|
17
18
|
import { messageSearchTextTable } from "../schema/message-data.js";
|
|
18
19
|
import { threadMessageTable } from "../schema/thread-message.js";
|
|
@@ -37,10 +38,7 @@ type AnyDb = RepositoryDb<Record<string, unknown>>;
|
|
|
37
38
|
|
|
38
39
|
type SearchDocument = typeof threadMessageSearchTable.$inferInsert;
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
// nine parameters, so ten documents per insert stay under it.
|
|
42
|
-
const DOCUMENTS_PER_STATEMENT = 10;
|
|
43
|
-
const DOCUMENTS_PER_REBUILD_PAGE = DOCUMENTS_PER_STATEMENT * 30;
|
|
41
|
+
const DOCUMENTS_PER_REBUILD_PAGE = 300;
|
|
44
42
|
|
|
45
43
|
// The document's rowid, derived from its thread message id so a write or a
|
|
46
44
|
// removal finds the document without a lookup. 53 bits, the integers a number
|
|
@@ -96,14 +94,6 @@ const toDocument = (row: IndexedRow): SearchDocument => ({
|
|
|
96
94
|
body: row.searchText ?? "",
|
|
97
95
|
});
|
|
98
96
|
|
|
99
|
-
const chunk = <T>(items: readonly T[], size: number): T[][] => {
|
|
100
|
-
const chunks: T[][] = [];
|
|
101
|
-
for (let start = 0; start < items.length; start += size) {
|
|
102
|
-
chunks.push(items.slice(start, start + size));
|
|
103
|
-
}
|
|
104
|
-
return chunks;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
97
|
/** Where a page of candidates resumes: the last one the previous page held. */
|
|
108
98
|
export interface SearchPosition {
|
|
109
99
|
rank: number;
|
|
@@ -141,6 +131,7 @@ export interface CandidateQuery {
|
|
|
141
131
|
export class SearchIndex {
|
|
142
132
|
private readonly search: AnyDb;
|
|
143
133
|
private readonly batch: WriteBatch;
|
|
134
|
+
private readonly maxBoundParameters: number;
|
|
144
135
|
|
|
145
136
|
constructor(
|
|
146
137
|
private readonly mail: AnyDb,
|
|
@@ -148,12 +139,17 @@ export class SearchIndex {
|
|
|
148
139
|
) {
|
|
149
140
|
this.search = store.db;
|
|
150
141
|
this.batch = store.batch;
|
|
142
|
+
this.maxBoundParameters = store.maxBoundParameters;
|
|
151
143
|
}
|
|
152
144
|
|
|
153
145
|
/** Make the index match the mail database for these thread messages. */
|
|
154
146
|
async sync(threadMessageIds: readonly string[]): Promise<void> {
|
|
155
147
|
const ids = [...new Set(threadMessageIds)];
|
|
156
|
-
for (const slice of
|
|
148
|
+
for (const slice of chunkRows(
|
|
149
|
+
threadMessageSearchTable,
|
|
150
|
+
ids,
|
|
151
|
+
this.maxBoundParameters,
|
|
152
|
+
)) {
|
|
157
153
|
const rows = await this.mail
|
|
158
154
|
.select(indexedColumns)
|
|
159
155
|
.from(threadMessageTable)
|
|
@@ -272,10 +268,11 @@ export class SearchIndex {
|
|
|
272
268
|
const last = rows.at(-1);
|
|
273
269
|
if (!last) return indexed;
|
|
274
270
|
await this.batch(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
.
|
|
278
|
-
|
|
271
|
+
chunkRows(threadMessageSearchTable, rows, this.maxBoundParameters).map(
|
|
272
|
+
(slice) =>
|
|
273
|
+
this.search
|
|
274
|
+
.insert(threadMessageSearchTable)
|
|
275
|
+
.values(slice.map(toDocument)),
|
|
279
276
|
),
|
|
280
277
|
);
|
|
281
278
|
indexed += rows.length;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SqliteLimits } from "./bound-parameters.js";
|
|
1
2
|
import {
|
|
2
3
|
type SqliteDatabases,
|
|
3
4
|
type SqliteStores,
|
|
@@ -67,7 +68,7 @@ export const composeSqliteRepositories = ({
|
|
|
67
68
|
accountExportRequest: new AccountExportRequestRepo(app.db),
|
|
68
69
|
configImport: new ConfigImportRepo(app.db),
|
|
69
70
|
quarantine: new QuarantineRepo(mail.db),
|
|
70
|
-
syncWarning: new SyncWarningRepo(mail
|
|
71
|
+
syncWarning: new SyncWarningRepo(mail),
|
|
71
72
|
organizeJobRequest: new OrganizeJobRequestRepo(mail.db),
|
|
72
73
|
placementMove: new MessagePlacementMoveRepo(mail.db),
|
|
73
74
|
flagPush: new MessageFlagPushRepo(mail.db),
|
|
@@ -77,7 +78,7 @@ export const composeSqliteRepositories = ({
|
|
|
77
78
|
senderSignerStanding: new SenderSignerStandingRepo(mail.db),
|
|
78
79
|
calendarCollection: new CalendarCollectionRepo(app.db),
|
|
79
80
|
calendarObject: new CalendarObjectRepo(app.db),
|
|
80
|
-
calendarEventIndex: new CalendarEventIndexRepo(app
|
|
81
|
+
calendarEventIndex: new CalendarEventIndexRepo(app),
|
|
81
82
|
calendarFeedToken: new CalendarFeedTokenRepo(app.db),
|
|
82
83
|
calendarSuggestion,
|
|
83
84
|
calendarStore: new DrizzleCalendarStore(app, calendarSuggestion),
|
|
@@ -88,4 +89,6 @@ export type SqliteRepositories = ReturnType<typeof composeSqliteRepositories>;
|
|
|
88
89
|
|
|
89
90
|
export const createSqliteRepositories = (
|
|
90
91
|
databases: SqliteDatabases,
|
|
91
|
-
|
|
92
|
+
limits: SqliteLimits,
|
|
93
|
+
): SqliteRepositories =>
|
|
94
|
+
composeSqliteRepositories(syncStores(databases, limits));
|