Skip to content

Commit cbd3324

Browse files
committed
Add tests to check Drizzle query concurrency.
1 parent 63a191a commit cbd3324

File tree

2 files changed

+150
-12
lines changed

2 files changed

+150
-12
lines changed

packages/node/tests/DrizzleNode.test.ts

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
22
import { eq, relations } from 'drizzle-orm';
33

4-
import { databaseTest } from './utils';
4+
import { customDatabaseTest, databaseTest } from './utils';
55
import { wrapPowerSyncWithDrizzle } from '@powersync/drizzle-driver';
66
import { PowerSyncDatabase } from '../lib';
77
import { expect } from 'vitest';
@@ -53,6 +53,15 @@ databaseTest('should retrieve a list with todos', async ({ database }) => {
5353
expect(result).toEqual([{ id: '1', name: 'list 1', todos: [{ id: '33', content: 'Post content', list_id: '1' }] }]);
5454
});
5555

56+
databaseTest('insert returning', async ({ database }) => {
57+
const db = await setupDrizzle(database);
58+
59+
// This is a special case since it's an insert query that returns values
60+
const result = await db.insert(drizzleLists).values({ id: '2', name: 'list 2' }).returning();
61+
62+
expect(result).toEqual([{ id: '2', name: 'list 2' }]);
63+
});
64+
5665
databaseTest('should retrieve a todo with its list', async ({ database }) => {
5766
const db = await setupDrizzle(database);
5867

@@ -97,3 +106,126 @@ databaseTest('should return a list and todos using fullJoin', async ({ database
97106
expect(result[0].lists).toEqual({ id: '1', name: 'list 1' });
98107
expect(result[0].todos).toEqual({ id: '33', content: 'Post content', list_id: '1' });
99108
});
109+
110+
customDatabaseTest({ database: { readWorkerCount: 2 } as any })(
111+
'should execute transactions concurrently',
112+
{},
113+
async ({ database }) => {
114+
// This test opens one write transaction and two read transactions, testing that they can all execute
115+
// concurrently.
116+
const db = await setupDrizzle(database);
117+
118+
const openedWrite = deferred();
119+
const openedRead1 = deferred();
120+
const openedRead2 = deferred();
121+
const completedWrite = deferred();
122+
123+
const t1 = db.transaction(async (tx) => {
124+
await tx.insert(drizzleLists).values({ id: '2', name: 'list 2' });
125+
126+
openedWrite.resolve();
127+
128+
await openedRead1.promise;
129+
await openedRead2.promise;
130+
131+
completedWrite.resolve();
132+
});
133+
134+
await openedWrite.promise;
135+
136+
const t2 = db.transaction(
137+
async (tx) => {
138+
const result = await tx.query.lists.findMany();
139+
expect(result).toEqual([{ id: '1', name: 'list 1' }]);
140+
openedRead1.resolve();
141+
await completedWrite.promise;
142+
},
143+
{ accessMode: 'read only' }
144+
);
145+
146+
const t3 = db.transaction(
147+
async (tx) => {
148+
await openedRead1.promise;
149+
const result = await tx.query.lists.findMany();
150+
expect(result).toEqual([{ id: '1', name: 'list 1' }]);
151+
152+
openedRead2.resolve();
153+
await completedWrite.promise;
154+
},
155+
{ accessMode: 'read only' }
156+
);
157+
158+
await Promise.all([t1, t2, t3]);
159+
160+
const result = await db.query.lists.findMany();
161+
expect(result).toEqual([
162+
{ id: '1', name: 'list 1' },
163+
{ id: '2', name: 'list 2' }
164+
]);
165+
}
166+
);
167+
168+
customDatabaseTest({ database: { readWorkerCount: 2 } as any })(
169+
'should execute select queries concurrently',
170+
async ({ database }) => {
171+
// This test opens one write transaction and two read transactions, testing that they can all execute
172+
// concurrently.
173+
const db = await setupDrizzle(database);
174+
175+
const openedWrite = deferred();
176+
const completedRead = deferred();
177+
const completedWrite = deferred();
178+
179+
const t1 = db.transaction(async (tx) => {
180+
await tx.insert(drizzleLists).values({ id: '2', name: 'list 2' });
181+
182+
openedWrite.resolve();
183+
184+
await completedRead.promise;
185+
186+
completedWrite.resolve();
187+
});
188+
189+
await openedWrite.promise;
190+
191+
const result1 = await db.select().from(drizzleLists);
192+
expect(result1).toEqual([{ id: '1', name: 'list 1' }]);
193+
194+
const result2 = await db
195+
.select()
196+
.from(drizzleLists)
197+
.fullJoin(drizzleTodos, eq(drizzleLists.id, drizzleTodos.list_id));
198+
199+
expect(result2[0].lists).toEqual({ id: '1', name: 'list 1' });
200+
expect(result2[0].todos).toEqual({ id: '33', content: 'Post content', list_id: '1' });
201+
202+
// Note: This case is not supported yet (drizzle 0.44.7), since it doesn't set
203+
// queryMetadata for these queries
204+
// const result3 = await db.query.lists.findMany();
205+
// expect(result3).toEqual([{ id: '1', name: 'list 1' }]);
206+
207+
completedRead.resolve();
208+
209+
await t1;
210+
211+
const resultAfter = await db.query.lists.findMany();
212+
expect(resultAfter).toEqual([
213+
{ id: '1', name: 'list 1' },
214+
{ id: '2', name: 'list 2' }
215+
]);
216+
}
217+
);
218+
219+
function deferred<T = void>() {
220+
let resolve: (value: T) => void;
221+
let reject: (err) => void;
222+
const promise = new Promise<T>((a, b) => {
223+
resolve = a;
224+
reject = b;
225+
});
226+
return {
227+
promise,
228+
resolve,
229+
reject
230+
};
231+
}

packages/node/tests/utils.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
PowerSyncBackendConnector,
1515
PowerSyncCredentials,
1616
PowerSyncDatabase,
17+
PowerSyncDatabaseOptions,
1718
Schema,
1819
StreamingSyncCheckpoint,
1920
StreamingSyncLine,
@@ -65,27 +66,32 @@ export async function createDatabase(
6566

6667
const database = new PowerSyncDatabase({
6768
schema: AppSchema,
69+
...options,
70+
logger: defaultLogger,
6871
database: {
6972
dbFilename: 'test.db',
7073
dbLocation: tmpdir,
7174
// Using a single read worker (instead of multiple, the default) seems to improve the reliability of tests in GH
7275
// actions. So far, we've not been able to reproduce these failures locally.
73-
readWorkerCount: 1
74-
},
75-
logger: defaultLogger,
76-
...options
76+
readWorkerCount: 1,
77+
...options.database
78+
}
7779
});
7880
await database.init();
7981
return database;
8082
}
8183

82-
export const databaseTest = tempDirectoryTest.extend<{ database: PowerSyncDatabase }>({
83-
database: async ({ tmpdir }, use) => {
84-
const db = await createDatabase(tmpdir);
85-
await use(db);
86-
await db.close();
87-
}
88-
});
84+
export const customDatabaseTest = (options?: Partial<NodePowerSyncDatabaseOptions>) => {
85+
return tempDirectoryTest.extend<{ database: PowerSyncDatabase }>({
86+
database: async ({ tmpdir }, use) => {
87+
const db = await createDatabase(tmpdir, options);
88+
await use(db);
89+
await db.close();
90+
}
91+
});
92+
};
93+
94+
export const databaseTest = customDatabaseTest();
8995

9096
// TODO: Unify this with the test setup for the web SDK.
9197
export const mockSyncServiceTest = tempDirectoryTest.extend<{

0 commit comments

Comments
 (0)