Skip to content

Commit 40ef683

Browse files
committed
Added replace many and deleteManyByIds to prepare for a proper batching in handle method
1 parent c618f16 commit 40ef683

9 files changed

Lines changed: 1161 additions & 117 deletions

File tree

src/packages/pongo/src/core/collection/pongoCollection.ts

Lines changed: 339 additions & 115 deletions
Large diffs are not rendered by default.

src/packages/pongo/src/core/collection/pongoCollectionSchema.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ export type PongoCollectionSQLBuilder = {
3333
options?: DeleteOneOptions,
3434
) => SQL;
3535
deleteMany: <T>(filter: PongoFilter<T> | SQL) => SQL;
36+
replaceMany: <T>(
37+
documents: Array<{
38+
_id: string;
39+
document: WithoutId<T>;
40+
_version?: bigint;
41+
}>,
42+
) => SQL;
43+
deleteManyByIds: (ids: Array<{ _id: string; _version?: bigint }>) => SQL;
3644
findOne: <T>(filter: PongoFilter<T> | SQL) => SQL;
3745
find: <T>(filter: PongoFilter<T> | SQL, options?: FindOptions) => SQL;
3846
countDocuments: <T>(filter: PongoFilter<T> | SQL) => SQL;

src/packages/pongo/src/core/typing/operations.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,10 @@ export type HandleOptions = {
192192
expectedVersion?: ExpectedDocumentVersion;
193193
} & CollectionOperationOptions;
194194

195+
export type BatchHandleOptions = {
196+
skipConcurrencyCheck?: boolean;
197+
} & CollectionOperationOptions;
198+
195199
export type ReplaceOneOptions = {
196200
expectedVersion?: Exclude<ExpectedDocumentVersion, 'DOCUMENT_DOES_NOT_EXIST'>;
197201
} & CollectionOperationOptions;
@@ -283,6 +287,23 @@ export interface PongoCollection<T extends PongoDocument> {
283287
handle: DocumentHandler<T>,
284288
options?: HandleOptions,
285289
): Promise<PongoHandleResult<T>>;
290+
handle(
291+
id: string[],
292+
handle: DocumentHandler<T>,
293+
options?: BatchHandleOptions,
294+
): Promise<PongoHandleResult<T>[]>;
295+
replaceMany(
296+
documents: Array<{
297+
_id: string;
298+
document: WithoutId<T>;
299+
_version?: bigint;
300+
}>,
301+
options?: CollectionOperationOptions,
302+
): Promise<PongoReplaceManyResult>;
303+
deleteManyByIds(
304+
ids: Array<{ _id: string; _version?: bigint }>,
305+
options?: CollectionOperationOptions,
306+
): Promise<PongoDeleteResult & { deletedIds: Set<string> }>;
286307
readonly schema: Readonly<{
287308
component: PongoCollectionSchemaComponent;
288309
migrate(options?: PongoMigrationOptions): Promise<RunSQLMigrationsResult>;
@@ -579,6 +600,13 @@ export interface PongoDeleteManyResult extends OperationResult {
579600
deletedCount: number;
580601
}
581602

603+
export interface PongoReplaceManyResult extends OperationResult {
604+
modifiedCount: number;
605+
matchedCount: number;
606+
modifiedIds: Set<string>;
607+
versions: Map<string, bigint>;
608+
}
609+
582610
export type PongoHandleResult<T> =
583611
| (PongoInsertOneResult & { document: T })
584612
| (PongoUpdateResult & { document: T })

src/packages/pongo/src/e2e/postgresql/pg/postgres.e2e.spec.ts

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,6 +1244,137 @@ describe('MongoDB Compatibility Tests', () => {
12441244
});
12451245
});
12461246

1247+
describe('Batch Handle Operations', () => {
1248+
it('should pass null to handle for non-existing documents', async () => {
1249+
const collection = pongoDb.collection<User>('batchHandleCollection');
1250+
const nonExistingId = uuid();
1251+
1252+
let receivedDoc: User | null = undefined as unknown as User | null;
1253+
await collection.handle([nonExistingId], (existing) => {
1254+
receivedDoc = existing;
1255+
return null;
1256+
});
1257+
1258+
assert.strictEqual(receivedDoc, null);
1259+
});
1260+
1261+
it('should insert new documents for non-existing ids', async () => {
1262+
const collection = pongoDb.collection<User>('batchHandleCollection');
1263+
const id1 = uuid();
1264+
const id2 = uuid();
1265+
1266+
const results = await collection.handle([id1, id2], (_existing) => ({
1267+
name: 'Batch User',
1268+
age: 10,
1269+
}));
1270+
1271+
assert(results.every((r) => r.successful));
1272+
1273+
const doc1 = await collection.findOne({ _id: id1 });
1274+
const doc2 = await collection.findOne({ _id: id2 });
1275+
1276+
assert.strictEqual(doc1?.name, 'Batch User');
1277+
assert.strictEqual(doc2?.name, 'Batch User');
1278+
});
1279+
1280+
it('should replace existing documents', async () => {
1281+
const collection = pongoDb.collection<User>('batchHandleCollection');
1282+
1283+
const doc: User = { name: 'Original', age: 1 };
1284+
const insertResult = await collection.insertOne(doc);
1285+
const id = insertResult.insertedId!;
1286+
1287+
const results = await collection.handle([id], (existing) =>
1288+
existing ? { ...existing, age: 99 } : null,
1289+
);
1290+
1291+
assert(results[0]?.successful);
1292+
1293+
const updated = await collection.findOne({ _id: id });
1294+
assert.strictEqual(updated?.age, 99);
1295+
assert.strictEqual(updated?._version, 2n);
1296+
});
1297+
1298+
it('should delete existing documents when handler returns null', async () => {
1299+
const collection = pongoDb.collection<User>('batchHandleCollection');
1300+
1301+
const doc1: User = { name: 'ToDelete1', age: 1 };
1302+
const doc2: User = { name: 'ToDelete2', age: 2 };
1303+
const r1 = await collection.insertOne(doc1);
1304+
const r2 = await collection.insertOne(doc2);
1305+
1306+
const results = await collection.handle(
1307+
[r1.insertedId!, r2.insertedId!],
1308+
() => null,
1309+
);
1310+
1311+
assert(results.every((r) => r.successful));
1312+
assert(results.every((r) => r.document === null));
1313+
1314+
assert.strictEqual(
1315+
await collection.findOne({ _id: r1.insertedId! }),
1316+
null,
1317+
);
1318+
assert.strictEqual(
1319+
await collection.findOne({ _id: r2.insertedId! }),
1320+
null,
1321+
);
1322+
});
1323+
1324+
it('should do nothing for unchanged documents', async () => {
1325+
const collection = pongoDb.collection<User>('batchHandleCollection');
1326+
1327+
const doc: User = { name: 'Unchanged', age: 5 };
1328+
const insertResult = await collection.insertOne(doc);
1329+
const id = insertResult.insertedId!;
1330+
1331+
const results = await collection.handle([id], (existing) => existing);
1332+
1333+
assert(results[0]?.successful);
1334+
1335+
const found = await collection.findOne({ _id: id });
1336+
assert.strictEqual(found?._version, 1n);
1337+
});
1338+
1339+
it('should preserve result order matching input id order', async () => {
1340+
const collection = pongoDb.collection<User>('batchHandleCollection');
1341+
1342+
const ids = [uuid(), uuid(), uuid()];
1343+
1344+
const results = await collection.handle(ids, (_existing) => ({
1345+
name: 'Ordered',
1346+
age: 0,
1347+
}));
1348+
1349+
assert.strictEqual(results.length, 3);
1350+
for (let i = 0; i < ids.length; i++) {
1351+
assert.strictEqual(results[i]?.document?._id, ids[i]);
1352+
}
1353+
});
1354+
1355+
it('should make changes when handler modifies existing documents', async () => {
1356+
const collection = pongoDb.collection<User>('batchHandleCollection');
1357+
1358+
const doc: User = { name: 'Mutable', age: 10 };
1359+
const insertResult = await collection.insertOne(doc);
1360+
const id = insertResult.insertedId!;
1361+
1362+
const results = await collection.handle([id], (existing) => {
1363+
if (!existing) return null;
1364+
return { ...existing, name: 'Modified', age: existing.age * 2 };
1365+
});
1366+
1367+
assert(results[0]?.successful);
1368+
assert.strictEqual(results[0]?.document?.name, 'Modified');
1369+
assert.strictEqual(results[0]?.document?.age, 20);
1370+
1371+
const persisted = await collection.findOne({ _id: id });
1372+
assert.strictEqual(persisted?.name, 'Modified');
1373+
assert.strictEqual(persisted?.age, 20);
1374+
assert.strictEqual(persisted?._version, 2n);
1375+
});
1376+
});
1377+
12471378
describe('No filter', () => {
12481379
it('should filter and count without filter specified', async () => {
12491380
const pongoCollection = pongoDb.collection<User>('nofilter');

0 commit comments

Comments
 (0)