@powersync/service-module-mongodb 0.21.0 → 0.21.1
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/CHANGELOG.md +18 -0
- package/dist/api/MongoRouteAPIAdapter.d.ts +2 -2
- package/dist/api/MongoRouteAPIAdapter.js +62 -142
- package/dist/api/MongoRouteAPIAdapter.js.map +1 -1
- package/dist/api/infer-collection-schema.d.ts +7 -0
- package/dist/api/infer-collection-schema.js +118 -0
- package/dist/api/infer-collection-schema.js.map +1 -0
- package/dist/replication/ChangeStream.js +1 -4
- package/dist/replication/ChangeStream.js.map +1 -1
- package/dist/replication/MongoSnapshotter.js +0 -3
- package/dist/replication/MongoSnapshotter.js.map +1 -1
- package/dist/replication/RawChangeStream.d.ts +3 -11
- package/dist/replication/RawChangeStream.js +0 -17
- package/dist/replication/RawChangeStream.js.map +1 -1
- package/package.json +8 -8
- package/src/api/MongoRouteAPIAdapter.ts +68 -126
- package/src/api/infer-collection-schema.ts +127 -0
- package/src/replication/ChangeStream.ts +1 -4
- package/src/replication/MongoSnapshotter.ts +0 -3
- package/src/replication/RawChangeStream.ts +3 -30
- package/test/src/documentdb_mode.test.ts +1 -2
- package/test/src/raw_change_stream.test.ts +0 -63
- package/test/src/schema.test.ts +180 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -213,7 +213,7 @@ export class ChangeStream {
|
|
|
213
213
|
this.isDocumentDb = await detectDocumentDb(this.defaultDb);
|
|
214
214
|
if (this.isDocumentDb) {
|
|
215
215
|
this.logger.warn(
|
|
216
|
-
'Azure DocumentDB support is
|
|
216
|
+
'Azure DocumentDB support is in alpha. APIs and behavior may change, and long-term stability is not yet guaranteed.'
|
|
217
217
|
);
|
|
218
218
|
}
|
|
219
219
|
this._checkpointImplementation = createCheckpointImplementation(this.isDocumentDb, {
|
|
@@ -595,9 +595,6 @@ export class ChangeStream {
|
|
|
595
595
|
return rawChangeStream(watchDb, pipeline, {
|
|
596
596
|
batchSize: options.batchSize ?? this.snapshotChunkLength,
|
|
597
597
|
maxAwaitTimeMS,
|
|
598
|
-
// maxAwaitTimeMS can be 0 for probe-style streams that do not want an idle wait.
|
|
599
|
-
// In that case there is no client-side wait to emulate for DocumentDB.
|
|
600
|
-
clientSideMaxAwaitTimeMS: this.isDocumentDb && maxAwaitTimeMS > 0,
|
|
601
598
|
maxTimeMS: this.changeStreamTimeout,
|
|
602
599
|
|
|
603
600
|
signal: options.signal,
|
|
@@ -785,9 +785,6 @@ export class MongoSnapshotter {
|
|
|
785
785
|
return rawChangeStream(watchDb, pipeline, {
|
|
786
786
|
batchSize: options.batchSize ?? this.snapshotChunkLength,
|
|
787
787
|
maxAwaitTimeMS,
|
|
788
|
-
// maxAwaitTimeMS can be 0 for probe-style streams that do not want an idle wait.
|
|
789
|
-
// In that case there is no client-side wait to emulate for DocumentDB.
|
|
790
|
-
clientSideMaxAwaitTimeMS: this.isDocumentDb && maxAwaitTimeMS > 0,
|
|
791
788
|
maxTimeMS: this.changeStreamTimeout,
|
|
792
789
|
signal: options.signal,
|
|
793
790
|
logger: this.logger,
|
|
@@ -6,14 +6,8 @@ import {
|
|
|
6
6
|
ReplicationAssertionError
|
|
7
7
|
} from '@powersync/lib-services-framework';
|
|
8
8
|
import { PerformanceTracer } from '@powersync/service-core';
|
|
9
|
-
import { performance } from 'node:perf_hooks';
|
|
10
|
-
import { setTimeout as delay } from 'node:timers/promises';
|
|
11
9
|
import { ChangeStreamInvalidatedError } from './ChangeStream.js';
|
|
12
10
|
|
|
13
|
-
// Keep the DocumentDB idle-poll workaround from adding the full maxAwaitTimeMS
|
|
14
|
-
// as local latency when an update arrives just after an empty batch.
|
|
15
|
-
const CLIENT_SIDE_MAX_AWAIT_TIME_MS_DELAY_CAP_MS = 1_000;
|
|
16
|
-
|
|
17
11
|
export interface RawChangeStreamOptions {
|
|
18
12
|
signal?: AbortSignal;
|
|
19
13
|
|
|
@@ -21,21 +15,12 @@ export interface RawChangeStreamOptions {
|
|
|
21
15
|
* How long to wait for new data per batch (max time for long-polling).
|
|
22
16
|
* This is sent as maxTimeMS for the getMore command.
|
|
23
17
|
*
|
|
24
|
-
* A value of 0
|
|
25
|
-
*
|
|
18
|
+
* A value of 0 removes the explicit await limit and leaves the server's
|
|
19
|
+
* default awaitData behavior in effect; it does not make getMore return
|
|
20
|
+
* immediately. Snapshot probes use 0 for this purpose.
|
|
26
21
|
*/
|
|
27
22
|
maxAwaitTimeMS: number;
|
|
28
23
|
|
|
29
|
-
/**
|
|
30
|
-
* Also enforce maxAwaitTimeMS on the client for empty getMore batches.
|
|
31
|
-
*
|
|
32
|
-
* Azure DocumentDB currently returns idle getMore calls before maxTimeMS. When
|
|
33
|
-
* this is enabled, empty batches are delayed locally (capped at 1s) to avoid
|
|
34
|
-
* tight polling. We still send maxTimeMS so this remains compatible with
|
|
35
|
-
* servers that handle maxAwaitTimeMS correctly.
|
|
36
|
-
*/
|
|
37
|
-
clientSideMaxAwaitTimeMS?: boolean;
|
|
38
|
-
|
|
39
24
|
/**
|
|
40
25
|
* Timeout for the initial aggregate command.
|
|
41
26
|
*/
|
|
@@ -230,17 +215,12 @@ async function* rawChangeStreamInner(
|
|
|
230
215
|
options.signal?.throwIfAborted();
|
|
231
216
|
|
|
232
217
|
using commandSpan = options.tracer?.span('changestream', 'getmore');
|
|
233
|
-
const getMoreStartedAt = performance.now();
|
|
234
218
|
const getMoreCommand: mongo.Document = {
|
|
235
219
|
getMore: cursorId,
|
|
236
220
|
collection: nsCollection,
|
|
237
221
|
batchSize: batchSizer.next(),
|
|
238
222
|
maxTimeMS: options.maxAwaitTimeMS
|
|
239
223
|
};
|
|
240
|
-
// Azure DocumentDB currently returns empty getMore batches before
|
|
241
|
-
// maxTimeMS expires. Keep maxTimeMS for forward compatibility with the
|
|
242
|
-
// server-side behavior, and when client-side mode is enabled, enforce the
|
|
243
|
-
// capped idle wait locally for empty batches below.
|
|
244
224
|
const getMoreResult: mongo.Document = await db.command(getMoreCommand, { session, raw: true }).catch((e) => {
|
|
245
225
|
if (isMongoServerError(e) && e.codeName == 'CursorKilled') {
|
|
246
226
|
// This may be due to the killCursors command issued when aborting.
|
|
@@ -268,13 +248,6 @@ async function* rawChangeStreamInner(
|
|
|
268
248
|
// postBatchResumeToken is returned in MongoDB 4.0.7 and later, and we support 6.0+
|
|
269
249
|
throw new ReplicationAssertionError(`postBatchResumeToken from aggregate response`);
|
|
270
250
|
}
|
|
271
|
-
if (options.clientSideMaxAwaitTimeMS && nextBatch.length == 0) {
|
|
272
|
-
const remainingMaxAwaitTimeMS = Math.ceil(options.maxAwaitTimeMS - (performance.now() - getMoreStartedAt));
|
|
273
|
-
if (remainingMaxAwaitTimeMS > 0) {
|
|
274
|
-
const clientSideDelayMs = Math.min(remainingMaxAwaitTimeMS, CLIENT_SIDE_MAX_AWAIT_TIME_MS_DELAY_CAP_MS);
|
|
275
|
-
await delay(clientSideDelayMs, undefined, { signal: options.signal });
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
251
|
yield {
|
|
279
252
|
events: nextBatch,
|
|
280
253
|
resumeToken: cursor.postBatchResumeToken,
|
|
@@ -759,8 +759,7 @@ bucket_definitions:
|
|
|
759
759
|
expect(JSON.parse(lastOp.data as string)).toMatchObject({ description: 'after_keepalive' });
|
|
760
760
|
});
|
|
761
761
|
|
|
762
|
-
|
|
763
|
-
test.skip('respects maxAwaitTimeMS for idle getMore calls in documentDbMode', async () => {
|
|
762
|
+
test('respects maxAwaitTimeMS for idle getMore calls in documentDbMode', async () => {
|
|
764
763
|
const maxAwaitTimeMS = 2_000;
|
|
765
764
|
|
|
766
765
|
await using context = await openContext({
|
|
@@ -5,7 +5,6 @@ import { getCursorBatchBytes } from '@module/replication/replication-index.js';
|
|
|
5
5
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
6
6
|
import { bson } from '@powersync/service-core';
|
|
7
7
|
import { DATABASE_TYPE, DatabaseType } from './DatabaseType.js';
|
|
8
|
-
import { testTimeout } from './test-timeouts.js';
|
|
9
8
|
import { clearTestDb, connectMongoData, requireFailCommand } from './util.js';
|
|
10
9
|
|
|
11
10
|
// DocumentDB only supports cluster-level change streams — collection- and
|
|
@@ -102,68 +101,6 @@ describe('internal mongodb utils', () => {
|
|
|
102
101
|
}
|
|
103
102
|
);
|
|
104
103
|
|
|
105
|
-
test(
|
|
106
|
-
'keeps getMore maxTimeMS when client-side maxAwaitTimeMS is enabled',
|
|
107
|
-
{ timeout: testTimeout(30_000, { cloudOverride: 150_000 }) },
|
|
108
|
-
async () => {
|
|
109
|
-
const { db, client } = await connectMongoData({ monitorCommands: true });
|
|
110
|
-
await using _ = { [Symbol.asyncDispose]: async () => await client.close() };
|
|
111
|
-
await clearTestDb(db);
|
|
112
|
-
const collection = db.collection('test_data');
|
|
113
|
-
// Keep the local Mongo path fast, but give Azure DocumentDB enough time
|
|
114
|
-
// for slow cloud change-stream delivery when maxTimeMS is sent to getMore.
|
|
115
|
-
const maxAwaitTimeMS = testTimeout(50, { cloudOverride: 10_000 });
|
|
116
|
-
|
|
117
|
-
const started: any[] = [];
|
|
118
|
-
client.on('commandStarted', (event) => {
|
|
119
|
-
if (event.commandName == 'aggregate' || event.commandName == 'getMore') {
|
|
120
|
-
started.push(event);
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
const stream = rawChangeStream(
|
|
125
|
-
DATABASE_TYPE == DatabaseType.DOCUMENTDB ? client.db('admin') : db,
|
|
126
|
-
[
|
|
127
|
-
{
|
|
128
|
-
$changeStream: {
|
|
129
|
-
fullDocument: 'updateLookup',
|
|
130
|
-
...(DATABASE_TYPE == DatabaseType.DOCUMENTDB ? { allChangesForCluster: true } : {})
|
|
131
|
-
}
|
|
132
|
-
},
|
|
133
|
-
...(DATABASE_TYPE == DatabaseType.DOCUMENTDB
|
|
134
|
-
? [
|
|
135
|
-
{
|
|
136
|
-
$match: {
|
|
137
|
-
'ns.db': db.databaseName,
|
|
138
|
-
'ns.coll': collection.collectionName
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
]
|
|
142
|
-
: [])
|
|
143
|
-
],
|
|
144
|
-
{
|
|
145
|
-
batchSize: 10,
|
|
146
|
-
maxAwaitTimeMS,
|
|
147
|
-
clientSideMaxAwaitTimeMS: true,
|
|
148
|
-
maxTimeMS: 1_000
|
|
149
|
-
}
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
await stream.next();
|
|
153
|
-
await collection.insertOne({ test: 1 });
|
|
154
|
-
const nextBatch = await readUntilNonEmptyBatch(stream);
|
|
155
|
-
await stream.return?.();
|
|
156
|
-
|
|
157
|
-
expect(nextBatch.events).toHaveLength(1);
|
|
158
|
-
|
|
159
|
-
const aggregate = started.find((event) => event.commandName == 'aggregate');
|
|
160
|
-
const getMore = started.find((event) => event.commandName == 'getMore');
|
|
161
|
-
|
|
162
|
-
expect(aggregate?.command.maxTimeMS).toEqual(1_000);
|
|
163
|
-
expect(getMore?.command.maxTimeMS).toEqual(maxAwaitTimeMS);
|
|
164
|
-
}
|
|
165
|
-
);
|
|
166
|
-
|
|
167
104
|
// This test uses configureFailPoint to inject a getMore timeout. Azure
|
|
168
105
|
// DocumentDB does not support configureFailPoint.
|
|
169
106
|
test.skipIf(DATABASE_TYPE == DatabaseType.DOCUMENTDB)(
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { inferCollectionSchema } from '@module/api/infer-collection-schema.js';
|
|
5
|
+
import { MongoRouteAPIAdapter } from '@module/api/MongoRouteAPIAdapter.js';
|
|
6
|
+
import { DATABASE_TYPE, DatabaseType } from './DatabaseType.js';
|
|
7
|
+
import { testTimeout } from './test-timeouts.js';
|
|
8
|
+
import { connectMongoData, requireFailCommand, TEST_CONNECTION_OPTIONS } from './util.js';
|
|
9
|
+
|
|
10
|
+
const isDocumentDb = DATABASE_TYPE == DatabaseType.DOCUMENTDB;
|
|
11
|
+
|
|
12
|
+
describe('collection schema inference', { timeout: testTimeout(20_000) }, () => {
|
|
13
|
+
let client: mongo.MongoClient;
|
|
14
|
+
let db: mongo.Db;
|
|
15
|
+
|
|
16
|
+
beforeEach(async () => {
|
|
17
|
+
({ client } = await connectMongoData({ monitorCommands: true }));
|
|
18
|
+
db = client.db(`${TEST_CONNECTION_OPTIONS.database}_schema`);
|
|
19
|
+
await db.dropDatabase();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(async () => {
|
|
23
|
+
await db.dropDatabase();
|
|
24
|
+
await client.close();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('merges types across documents and only infers top-level fields', async () => {
|
|
28
|
+
const collection = db.collection('mixed');
|
|
29
|
+
await collection.insertMany([
|
|
30
|
+
{
|
|
31
|
+
_id: 1 as any,
|
|
32
|
+
value: null,
|
|
33
|
+
whole: new mongo.Double(42),
|
|
34
|
+
huge: new mongo.Double(1e100),
|
|
35
|
+
fractional: new mongo.Double(-1.25),
|
|
36
|
+
nan: NaN,
|
|
37
|
+
positiveInfinity: Infinity,
|
|
38
|
+
negativeInfinity: -Infinity,
|
|
39
|
+
long: mongo.Long.fromString('9007199254740993'),
|
|
40
|
+
array: [{ nested: 'value' }],
|
|
41
|
+
object: { nested: true }
|
|
42
|
+
},
|
|
43
|
+
{ _id: 2 as any, value: 'text', whole: new mongo.Int32(-42), array: [] },
|
|
44
|
+
{ _id: 3 as any, value: 42, whole: new mongo.Double(-0) },
|
|
45
|
+
{ _id: 4 as any, value: 1.25 },
|
|
46
|
+
{ _id: 5 as any, value: new mongo.Double(42) }
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
expect(await inferCollectionSchema(collection, isDocumentDb)).toMatchObject([
|
|
50
|
+
{ name: '_id', sqlite_type: 4, internal_type: 'Integer' },
|
|
51
|
+
{ name: 'array', sqlite_type: 2, internal_type: 'Array' },
|
|
52
|
+
{ name: 'fractional', sqlite_type: 8, internal_type: 'Double' },
|
|
53
|
+
{ name: 'huge', sqlite_type: 4, internal_type: 'Integer' },
|
|
54
|
+
{ name: 'long', sqlite_type: 4, internal_type: 'Long' },
|
|
55
|
+
{ name: 'nan', sqlite_type: 8, internal_type: 'Double' },
|
|
56
|
+
{ name: 'negativeInfinity', sqlite_type: 8, internal_type: 'Double' },
|
|
57
|
+
{ name: 'object', sqlite_type: 2, internal_type: 'Object' },
|
|
58
|
+
{ name: 'positiveInfinity', sqlite_type: 8, internal_type: 'Double' },
|
|
59
|
+
{ name: 'value', sqlite_type: 14, internal_type: 'Double | Integer | Null | String' },
|
|
60
|
+
{ name: 'whole', sqlite_type: 4, internal_type: 'Integer' }
|
|
61
|
+
]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('distinguishes UUIDs from binary values by subtype and length', async () => {
|
|
65
|
+
const collection = db.collection('binary');
|
|
66
|
+
await collection.insertMany([
|
|
67
|
+
{
|
|
68
|
+
_id: 1 as any,
|
|
69
|
+
uuid: new mongo.UUID('00000000-0000-0000-0000-000000000000'),
|
|
70
|
+
binary: new mongo.Binary(Buffer.alloc(16)),
|
|
71
|
+
mixed: new mongo.UUID()
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
_id: 2 as any,
|
|
75
|
+
uuid: new mongo.UUID('ffffffff-ffff-ffff-ffff-ffffffffffff'),
|
|
76
|
+
binary: new mongo.Binary(Buffer.alloc(16, 255)),
|
|
77
|
+
mixed: new mongo.Binary(Buffer.alloc(16), mongo.Binary.SUBTYPE_UUID_OLD)
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
_id: 3 as any,
|
|
81
|
+
uuid: new mongo.UUID(),
|
|
82
|
+
binary: new mongo.Binary(Buffer.alloc(16), mongo.Binary.SUBTYPE_MD5)
|
|
83
|
+
},
|
|
84
|
+
{ _id: 4 as any, binary: new mongo.Binary(Buffer.alloc(15, 255), mongo.Binary.SUBTYPE_USER_DEFINED) },
|
|
85
|
+
{ _id: 5 as any, binary: new mongo.Binary(Buffer.alloc(17)) },
|
|
86
|
+
{ _id: 6 as any, binary: new mongo.Binary(Buffer.alloc(0)) }
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
expect(await inferCollectionSchema(collection, isDocumentDb)).toMatchObject([
|
|
90
|
+
{ name: '_id', sqlite_type: 4, internal_type: 'Integer' },
|
|
91
|
+
{ name: 'binary', sqlite_type: 1, internal_type: 'Binary' },
|
|
92
|
+
{ name: 'mixed', sqlite_type: 3, internal_type: 'Binary | UUID' },
|
|
93
|
+
{ name: 'uuid', sqlite_type: 2, internal_type: 'UUID' }
|
|
94
|
+
]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('handles empty collections and documents with only an id', async () => {
|
|
98
|
+
const collection = await db.createCollection('empty');
|
|
99
|
+
expect(await inferCollectionSchema(collection, isDocumentDb)).toEqual([]);
|
|
100
|
+
|
|
101
|
+
await collection.insertOne({});
|
|
102
|
+
expect(await inferCollectionSchema(collection, isDocumentDb)).toEqual([
|
|
103
|
+
{ name: '_id', sqlite_type: 2, type: 'ObjectId', internal_type: 'ObjectId', pg_type: 'ObjectId' }
|
|
104
|
+
]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test.skipIf(DATABASE_TYPE == DatabaseType.DOCUMENTDB)(
|
|
108
|
+
'preserves field names with a non-simple collation',
|
|
109
|
+
async () => {
|
|
110
|
+
const collection = await db.createCollection('collation', { collation: { locale: 'en', strength: 1 } });
|
|
111
|
+
await collection.insertOne({ Name: 'text', name: 1, 'with.dot': true, $field: [] });
|
|
112
|
+
|
|
113
|
+
const columns = await inferCollectionSchema(collection, isDocumentDb);
|
|
114
|
+
expect(columns).toEqual(
|
|
115
|
+
expect.arrayContaining([
|
|
116
|
+
expect.objectContaining({ name: 'Name', sqlite_type: 2 }),
|
|
117
|
+
expect.objectContaining({ name: 'name', sqlite_type: 4 }),
|
|
118
|
+
expect.objectContaining({ name: 'with.dot', sqlite_type: 4 }),
|
|
119
|
+
expect.objectContaining({ name: '$field', internal_type: 'Array' })
|
|
120
|
+
])
|
|
121
|
+
);
|
|
122
|
+
expect(columns).toHaveLength(5);
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
test('returns only small schema metadata for multi-megabyte documents', async () => {
|
|
127
|
+
const collection = db.collection('large');
|
|
128
|
+
const largeString = 'x'.repeat(2 * 1024 * 1024);
|
|
129
|
+
for (let i = 0; i < 4; i++) {
|
|
130
|
+
await collection.insertOne({ text: largeString, binary: Buffer.alloc(2 * 1024 * 1024), array: [largeString] });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const responseSizes: number[] = [];
|
|
134
|
+
const executionLimits: number[] = [];
|
|
135
|
+
client.on('commandStarted', (event: mongo.CommandStartedEvent) => {
|
|
136
|
+
if (event.commandName == 'aggregate') {
|
|
137
|
+
executionLimits.push(event.command.maxTimeMS);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
client.on('commandSucceeded', (event: mongo.CommandSucceededEvent) => {
|
|
141
|
+
if (event.commandName == 'aggregate' || event.commandName == 'getMore') {
|
|
142
|
+
responseSizes.push(mongo.BSON.calculateObjectSize(event.reply as mongo.Document));
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
expect(await inferCollectionSchema(collection, isDocumentDb)).toMatchObject([
|
|
147
|
+
{ name: '_id', sqlite_type: 2, internal_type: 'ObjectId' },
|
|
148
|
+
{ name: 'array', sqlite_type: 2, internal_type: 'Array' },
|
|
149
|
+
{ name: 'binary', sqlite_type: 1, internal_type: 'Binary' },
|
|
150
|
+
{ name: 'text', sqlite_type: 2, internal_type: 'String' }
|
|
151
|
+
]);
|
|
152
|
+
expect(responseSizes.length).toBeGreaterThan(0);
|
|
153
|
+
expect(Math.max(...responseSizes)).toBeLessThan(4096);
|
|
154
|
+
expect(executionLimits).toEqual([30_000]);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test.skipIf(DATABASE_TYPE == DatabaseType.DOCUMENTDB)('fails schema inference on a query timeout', async (ctx) => {
|
|
158
|
+
await db.collection('timeout').insertOne({ value: 'text' });
|
|
159
|
+
await using adapter = new MongoRouteAPIAdapter({
|
|
160
|
+
type: 'mongodb',
|
|
161
|
+
...TEST_CONNECTION_OPTIONS,
|
|
162
|
+
database: db.databaseName
|
|
163
|
+
});
|
|
164
|
+
await using failCommand = await requireFailCommand(client, ctx);
|
|
165
|
+
await failCommand.configure({
|
|
166
|
+
mode: { times: 1 },
|
|
167
|
+
data: {
|
|
168
|
+
failCommands: ['aggregate'],
|
|
169
|
+
errorCode: 50 // MaxTimeMSExpired
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Exercise the real server/driver error path without waiting for the full timeout.
|
|
174
|
+
await expect(adapter.getConnectionSchema()).rejects.toMatchObject({
|
|
175
|
+
name: 'MongoServerError',
|
|
176
|
+
code: 50,
|
|
177
|
+
codeName: 'MaxTimeMSExpired'
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|