Skip to content

Commit 10362d8

Browse files
authored
returnDocumentResponses implementation (#67)
* Ok * returnDocumentResponses implementation + partial docs
1 parent 8990a0d commit 10362d8

File tree

4 files changed

+122
-52
lines changed

4 files changed

+122
-52
lines changed

src/data-api/collection.ts

Lines changed: 75 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,12 @@
1414

1515
import { normalizeSort } from './utils';
1616
import { FindCursor } from '@/src/data-api/cursor';
17-
import { Db, SomeDoc, SomeId } from '@/src/data-api';
17+
import { DataAPIDetailedErrorDescriptor, Db, SomeDoc, SomeId } from '@/src/data-api';
1818
import {
1919
BulkWriteError,
2020
CollectionNotFoundError,
2121
DataAPIResponseError,
22-
DeleteManyError,
23-
InsertManyError,
22+
DeleteManyError, InsertManyError,
2423
mkRespErrorFromResponse,
2524
mkRespErrorFromResponses,
2625
TooManyDocumentsToCountError,
@@ -73,7 +72,7 @@ import { FindOneAndReplaceCommand } from '@/src/data-api/types/find/find-one-rep
7372
import { DeleteOneCommand } from '@/src/data-api/types/delete/delete-one';
7473
import { FindOneAndDeleteCommand } from '@/src/data-api/types/find/find-one-delete';
7574
import { FindOneAndUpdateCommand } from '@/src/data-api/types/find/find-one-update';
76-
import { InsertManyCommand } from '@/src/data-api/types/insert/insert-many';
75+
import { InsertManyCommand, InsertManyDocumentResponse } from '@/src/data-api/types/insert/insert-many';
7776
import { Mutable } from '@/src/data-api/types/utils';
7877
import { CollectionSpawnOptions } from '@/src/data-api/types/collections/spawn-collection';
7978

@@ -474,7 +473,7 @@ export class Collection<Schema extends SomeDoc = SomeDoc> {
474473
commonResult.matchedCount += desc.rawResponse.status?.matchedCount ?? 0;
475474
commonResult.upsertedCount = desc.rawResponse.status?.upsertedCount ?? 0;
476475

477-
throw mkRespErrorFromResponse(UpdateManyError, command, desc.rawResponse, commonResult);
476+
throw mkRespErrorFromResponse(UpdateManyError, command, desc.rawResponse, { partialResult: commonResult });
478477
}
479478

480479
return (resp.status?.upsertedId)
@@ -677,8 +676,14 @@ export class Collection<Schema extends SomeDoc = SomeDoc> {
677676
if (!(e instanceof DataAPIResponseError)) {
678677
throw e;
679678
}
679+
680680
const desc = e.detailedErrorDescriptors[0];
681-
throw mkRespErrorFromResponse(DeleteManyError, command, desc.rawResponse, { deletedCount: numDeleted + (desc.rawResponse.status?.deletedCount ?? 0) })
681+
682+
throw mkRespErrorFromResponse(DeleteManyError, command, desc.rawResponse, {
683+
partialResult: {
684+
deletedCount: numDeleted + (desc.rawResponse.status?.deletedCount ?? 0),
685+
},
686+
});
682687
}
683688

684689
return {
@@ -1516,35 +1521,37 @@ const coalesceVectorSpecialsIntoSort = <T extends OptionsWithSort>(options: T |
15161521

15171522
// -- Insert Many ------------------------------------------------------------------------------------------
15181523

1519-
const insertManyOrdered = async <Schema>(httpClient: DataAPIHttpClient, documents: unknown[], chunkSize: number, timeoutManager: TimeoutManager): Promise<IdOf<Schema>[]> => {
1524+
const insertManyOrdered = async <Schema extends SomeDoc>(httpClient: DataAPIHttpClient, documents: unknown[], chunkSize: number, timeoutManager: TimeoutManager): Promise<IdOf<Schema>[]> => {
15201525
const insertedIds: IdOf<Schema>[] = [];
15211526

15221527
for (let i = 0, n = documents.length; i < n; i += chunkSize) {
15231528
const slice = documents.slice(i, i + chunkSize);
15241529

1525-
try {
1526-
const inserted = await insertMany<Schema>(httpClient, slice, true, timeoutManager);
1527-
insertedIds.push(...inserted);
1528-
} catch (e) {
1529-
if (!(e instanceof DataAPIResponseError)) {
1530-
throw e;
1531-
}
1532-
const desc = e.detailedErrorDescriptors[0];
1530+
const [docResp, inserted, errDesc] = await insertMany<Schema>(httpClient, slice, true, timeoutManager);
1531+
insertedIds.push(...inserted);
15331532

1534-
insertedIds.push(...desc.rawResponse.status?.insertedIds ?? []);
1535-
throw mkRespErrorFromResponse(InsertManyError, desc.command, desc.rawResponse, { insertedIds: insertedIds as SomeId[], insertedCount: insertedIds.length })
1533+
if (errDesc) {
1534+
throw mkRespErrorFromResponse(InsertManyError, errDesc.command, errDesc.rawResponse, {
1535+
partialResult: {
1536+
insertedIds: insertedIds as SomeId[],
1537+
insertedCount: insertedIds.length,
1538+
},
1539+
documentResponses: docResp,
1540+
failedCount: docResp.length - insertedIds.length,
1541+
});
15361542
}
15371543
}
15381544

15391545
return insertedIds;
15401546
}
15411547

1542-
const insertManyUnordered = async <Schema>(httpClient: DataAPIHttpClient, documents: unknown[], concurrency: number, chunkSize: number, timeoutManager: TimeoutManager): Promise<IdOf<Schema>[]> => {
1548+
const insertManyUnordered = async <Schema extends SomeDoc>(httpClient: DataAPIHttpClient, documents: unknown[], concurrency: number, chunkSize: number, timeoutManager: TimeoutManager): Promise<IdOf<Schema>[]> => {
15431549
const insertedIds: IdOf<Schema>[] = [];
15441550
let masterIndex = 0;
15451551

15461552
const failCommands = [] as Record<string, any>[];
15471553
const failRaw = [] as Record<string, any>[];
1554+
const docResps = [] as InsertManyDocumentResponse<SomeDoc>[];
15481555

15491556
const workers = Array.from({ length: concurrency }, async () => {
15501557
while (masterIndex < documents.length) {
@@ -1558,42 +1565,72 @@ const insertManyUnordered = async <Schema>(httpClient: DataAPIHttpClient, docume
15581565

15591566
const slice = documents.slice(localI, endIdx);
15601567

1561-
try {
1562-
const inserted = await insertMany<Schema>(httpClient, slice, false, timeoutManager);
1563-
insertedIds.push(...inserted);
1564-
} catch (e) {
1565-
if (!(e instanceof DataAPIResponseError)) {
1566-
throw e;
1567-
}
1568-
const desc = e.detailedErrorDescriptors[0];
1569-
1570-
const justInserted = desc.rawResponse.status?.insertedIds ?? [];
1571-
insertedIds.push(...justInserted);
1568+
const [docResp, inserted, errDesc] = await insertMany<Schema>(httpClient, slice, false, timeoutManager);
1569+
insertedIds.push(...inserted);
1570+
docResps.push(...docResp);
15721571

1573-
failCommands.push(desc.command);
1574-
failRaw.push(desc.rawResponse);
1572+
if (errDesc) {
1573+
failCommands.push(errDesc.command);
1574+
failRaw.push(errDesc.rawResponse);
15751575
}
15761576
}
15771577
});
15781578
await Promise.all(workers);
15791579

15801580
if (failCommands.length > 0) {
1581-
throw mkRespErrorFromResponses(InsertManyError, failCommands, failRaw, { insertedIds: insertedIds as SomeId[], insertedCount: insertedIds.length });
1581+
throw mkRespErrorFromResponses(InsertManyError, failCommands, failRaw, {
1582+
partialResult: {
1583+
insertedIds: insertedIds as SomeId[],
1584+
insertedCount: insertedIds.length,
1585+
},
1586+
documentResponses: docResps,
1587+
failedCount: docResps.length - insertedIds.length,
1588+
});
15821589
}
15831590

15841591
return insertedIds;
15851592
}
15861593

1587-
const insertMany = async <Schema>(httpClient: DataAPIHttpClient, documents: unknown[], ordered: boolean, timeoutManager: TimeoutManager): Promise<IdOf<Schema>[]> => {
1594+
const insertMany = async <Schema extends SomeDoc>(httpClient: DataAPIHttpClient, documents: unknown[], ordered: boolean, timeoutManager: TimeoutManager): Promise<[InsertManyDocumentResponse<Schema>[], IdOf<Schema>[], DataAPIDetailedErrorDescriptor | undefined]> => {
15881595
const command: InsertManyCommand = {
15891596
insertMany: {
15901597
documents,
1591-
options: { ordered },
1598+
options: {
1599+
returnDocumentResponses: true,
1600+
ordered,
1601+
},
15921602
}
15931603
}
15941604

1595-
const resp = await httpClient.executeCommand(command, { timeoutManager });
1596-
return resp.status?.insertedIds ?? [];
1605+
let resp, err: DataAPIResponseError | undefined;
1606+
1607+
try {
1608+
resp = await httpClient.executeCommand(command, { timeoutManager });
1609+
} catch (e) {
1610+
if (! (e instanceof DataAPIResponseError)) {
1611+
throw e;
1612+
}
1613+
resp = e.detailedErrorDescriptors[0].rawResponse;
1614+
err = e;
1615+
}
1616+
1617+
const documentResponses = resp.status!.documentResponses;
1618+
const errors = resp.errors!;
1619+
1620+
const insertedIds = [];
1621+
1622+
for (let i = 0, n = documentResponses.length; i < n; i++) {
1623+
const resp = documentResponses[i];
1624+
1625+
if (resp.status === "OK") {
1626+
insertedIds.push(resp._id);
1627+
} else if (resp.errorIdx) {
1628+
resp.error = errors[resp.errorIdx];
1629+
delete resp.errorIdx;
1630+
}
1631+
}
1632+
1633+
return [documentResponses, insertedIds, err?.detailedErrorDescriptors[0]];
15971634
}
15981635

15991636
// -- Bulk Write ------------------------------------------------------------------------------------------
@@ -1616,7 +1653,7 @@ const bulkWriteOrdered = async <Schema extends SomeDoc>(httpClient: DataAPIHttpC
16161653
addToBulkWriteResult(results, desc.rawResponse.status, i)
16171654
}
16181655

1619-
throw mkRespErrorFromResponse(BulkWriteError, desc.command, desc.rawResponse, results);
1656+
throw mkRespErrorFromResponse(BulkWriteError, desc.command, desc.rawResponse, { partialResult: results });
16201657
}
16211658

16221659
return results;
@@ -1654,7 +1691,7 @@ const bulkWriteUnordered = async <Schema extends SomeDoc>(httpClient: DataAPIHtt
16541691
await Promise.all(workers);
16551692

16561693
if (failCommands.length > 0) {
1657-
throw mkRespErrorFromResponses(BulkWriteError, failCommands, failRaw, results);
1694+
throw mkRespErrorFromResponses(BulkWriteError, failCommands, failRaw, { partialResult: results });
16581695
}
16591696

16601697
return results;

src/data-api/errors.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import type { InsertManyResult } from '@/src/data-api/types/insert/insert-many';
15+
import type { InsertManyDocumentResponse, InsertManyResult } from '@/src/data-api/types/insert/insert-many';
1616
import type { DeleteManyResult } from '@/src/data-api/types/delete/delete-many';
1717
import type { UpdateManyResult } from '@/src/data-api/types/update/update-many';
1818
import type { BulkWriteResult } from '@/src/data-api/types/misc/bulk-write';
@@ -449,6 +449,19 @@ export class InsertManyError extends CumulativeDataAPIError {
449449
* of all successful insertions.
450450
*/
451451
declare public readonly partialResult: InsertManyResult<SomeDoc>;
452+
453+
/**
454+
* The specific statuses and ids for each document present in the `insertMany` command
455+
*
456+
* The position of each document response is the same as its corresponding document in the input `documents` array
457+
*/
458+
declare public readonly documentResponses: InsertManyDocumentResponse<SomeDoc>[];
459+
460+
/**
461+
* The number of documents which failed insertion (i.e. their status in {@link InsertManyError.documentResponses} was
462+
* `'ERROR'` or `'SKIPPED'`)
463+
*/
464+
declare public readonly failedCount: number;
452465
}
453466

454467
/**
@@ -532,19 +545,17 @@ export class BulkWriteError extends CumulativeDataAPIError {
532545
declare public readonly partialResult: BulkWriteResult<SomeDoc>;
533546
}
534547

535-
type InferPartialResult<T> = T extends { readonly partialResult: infer P } ? P : never;
536-
537548
/**
538549
* @internal
539550
*/
540-
export const mkRespErrorFromResponse = <E extends DataAPIResponseError>(err: new (message: string, errorDescriptors: DataAPIErrorDescriptor[], detailedErrorDescriptors: DataAPIDetailedErrorDescriptor[]) => E, command: Record<string, any>, raw: RawDataAPIResponse, partialResult?: InferPartialResult<E>) => {
541-
return mkRespErrorFromResponses(err, [command], [raw], partialResult);
551+
export const mkRespErrorFromResponse = <E extends DataAPIResponseError>(err: new (message: string, errorDescriptors: DataAPIErrorDescriptor[], detailedErrorDescriptors: DataAPIDetailedErrorDescriptor[]) => E, command: Record<string, any>, raw: RawDataAPIResponse, attributes?: Omit<E, keyof DataAPIResponseError>) => {
552+
return mkRespErrorFromResponses(err, [command], [raw], attributes);
542553
}
543554

544555
/**
545556
* @internal
546557
*/
547-
export const mkRespErrorFromResponses = <E extends DataAPIResponseError>(err: new (message: string, errorDescriptors: DataAPIErrorDescriptor[], detailedErrorDescriptors: DataAPIDetailedErrorDescriptor[]) => E, commands: Record<string, any>[], raw: RawDataAPIResponse[], partialResult?: InferPartialResult<E>) => {
558+
export const mkRespErrorFromResponses = <E extends DataAPIResponseError>(err: new (message: string, errorDescriptors: DataAPIErrorDescriptor[], detailedErrorDescriptors: DataAPIDetailedErrorDescriptor[]) => E, commands: Record<string, any>[], raw: RawDataAPIResponse[], attributes?: Omit<E, keyof DataAPIResponseError>) => {
548559
const detailedDescriptors = [] as DataAPIDetailedErrorDescriptor[];
549560

550561
for (let i = 0, n = commands.length; i < n; i++) {
@@ -564,14 +575,9 @@ export const mkRespErrorFromResponses = <E extends DataAPIResponseError>(err: ne
564575
}
565576

566577
const errorDescriptors = detailedDescriptors.flatMap(d => d.errorDescriptors);
567-
568578
const message = errorDescriptors[0]?.message || 'Something unexpected occurred';
569579

570580
const instance = new err(message, errorDescriptors, detailedDescriptors) ;
571-
572-
if (partialResult) {
573-
/* @ts-expect-error - If the lord wants a partialResult, the lord will get a partialResult. */
574-
instance.partialResult = partialResult;
575-
}
581+
Object.assign(instance, attributes ?? {});
576582
return instance;
577583
}

src/data-api/types/insert/insert-many.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,16 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import type { IdOf } from '@/src/data-api/types';
15+
import { IdOf, SomeDoc } from '@/src/data-api/types';
1616
import { WithTimeout } from '@/src/common/types';
17-
import { SomeDoc } from '@/src/data-api';
17+
import { DataAPIErrorDescriptor } from '@/src/data-api';
1818

1919
/** @internal */
2020
export interface InsertManyCommand {
2121
insertMany: {
2222
documents: unknown[],
2323
options?: {
24+
returnDocumentResponses: true,
2425
ordered?: boolean,
2526
},
2627
}
@@ -176,3 +177,27 @@ export interface InsertManyResult<Schema extends SomeDoc> {
176177
*/
177178
insertedCount: number;
178179
}
180+
181+
/**
182+
* Represents the specific status and id for a document present in the `insertMany` command
183+
*/
184+
export interface InsertManyDocumentResponse<Schema extends SomeDoc> {
185+
/**
186+
* The exact value of the `_id` field of the document that was inserted, whether it be the value passed by the client,
187+
* or a server generated ID.
188+
*/
189+
_id: IdOf<Schema>,
190+
/**
191+
* The processing status of the document
192+
* - `OK`: The document was successfully processed, in which case the `error` field will be undefined for this object
193+
* - `ERROR`: There was an error processing the document, in which case the `error` field will be present for this object
194+
* - `SKIPPED`: The document was not processed because either the `insertMany` command was processing documents in order
195+
* which means the processing fails at the first failure, or some other failure occurred before this document was
196+
* processed. The `error` field will be undefined for this object.
197+
*/
198+
status: 'OK' | 'ERROR' | 'SKIPPED',
199+
/**
200+
* The error which caused this document to fail insertion.
201+
*/
202+
error?: DataAPIErrorDescriptor,
203+
}

tests/integration/data-api/collection/insert-many.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ parallel('integration.data-api.collection.insert-many', { truncateColls: 'defaul
173173
assert.ok(error instanceof InsertManyError);
174174
assert.strictEqual(error.errorDescriptors[0].errorCode, 'DOCUMENT_ALREADY_EXISTS');
175175
assert.strictEqual(error.partialResult.insertedCount, 10);
176+
assert.strictEqual(error.failedCount, 10);
176177
docs.slice(0, 10).forEach((doc, index) => {
177178
assert.strictEqual(error.partialResult.insertedIds[index], doc._id);
178179
});
@@ -191,6 +192,7 @@ parallel('integration.data-api.collection.insert-many', { truncateColls: 'defaul
191192
assert.ok(error instanceof InsertManyError);
192193
assert.strictEqual(error.errorDescriptors[0].errorCode, 'DOCUMENT_ALREADY_EXISTS');
193194
assert.strictEqual(error.partialResult.insertedCount, 19);
195+
assert.strictEqual(error.failedCount, 1);
194196
docs.slice(0, 9).concat(docs.slice(10)).forEach((doc) => {
195197
assert.ok(error.partialResult.insertedIds.includes(doc._id));
196198
});

0 commit comments

Comments
 (0)