db.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. import { Admin } from './admin';
  2. import { type BSONSerializeOptions, type Document, resolveBSONOptions } from './bson';
  3. import { ChangeStream, type ChangeStreamDocument, type ChangeStreamOptions } from './change_stream';
  4. import { Collection, type CollectionOptions } from './collection';
  5. import * as CONSTANTS from './constants';
  6. import { AggregationCursor } from './cursor/aggregation_cursor';
  7. import { ListCollectionsCursor } from './cursor/list_collections_cursor';
  8. import { RunCommandCursor, type RunCursorCommandOptions } from './cursor/run_command_cursor';
  9. import { MongoAPIError, MongoInvalidArgumentError } from './error';
  10. import type { MongoClient, PkFactory } from './mongo_client';
  11. import type { TODO_NODE_3286 } from './mongo_types';
  12. import { AddUserOperation, type AddUserOptions } from './operations/add_user';
  13. import type { AggregateOptions } from './operations/aggregate';
  14. import { CollectionsOperation } from './operations/collections';
  15. import type { IndexInformationOptions } from './operations/common_functions';
  16. import {
  17. CreateCollectionOperation,
  18. type CreateCollectionOptions
  19. } from './operations/create_collection';
  20. import {
  21. DropCollectionOperation,
  22. type DropCollectionOptions,
  23. DropDatabaseOperation,
  24. type DropDatabaseOptions
  25. } from './operations/drop';
  26. import { executeOperation } from './operations/execute_operation';
  27. import {
  28. type CreateIndexesOptions,
  29. CreateIndexOperation,
  30. IndexInformationOperation,
  31. type IndexSpecification
  32. } from './operations/indexes';
  33. import type { CollectionInfo, ListCollectionsOptions } from './operations/list_collections';
  34. import { ProfilingLevelOperation, type ProfilingLevelOptions } from './operations/profiling_level';
  35. import { RemoveUserOperation, type RemoveUserOptions } from './operations/remove_user';
  36. import { RenameOperation, type RenameOptions } from './operations/rename';
  37. import { RunCommandOperation, type RunCommandOptions } from './operations/run_command';
  38. import {
  39. type ProfilingLevel,
  40. SetProfilingLevelOperation,
  41. type SetProfilingLevelOptions
  42. } from './operations/set_profiling_level';
  43. import { DbStatsOperation, type DbStatsOptions } from './operations/stats';
  44. import { ReadConcern } from './read_concern';
  45. import { ReadPreference, type ReadPreferenceLike } from './read_preference';
  46. import { DEFAULT_PK_FACTORY, filterOptions, MongoDBNamespace, resolveOptions } from './utils';
  47. import { WriteConcern, type WriteConcernOptions } from './write_concern';
  48. // Allowed parameters
  49. const DB_OPTIONS_ALLOW_LIST = [
  50. 'writeConcern',
  51. 'readPreference',
  52. 'readPreferenceTags',
  53. 'native_parser',
  54. 'forceServerObjectId',
  55. 'pkFactory',
  56. 'serializeFunctions',
  57. 'raw',
  58. 'authSource',
  59. 'ignoreUndefined',
  60. 'readConcern',
  61. 'retryMiliSeconds',
  62. 'numberOfRetries',
  63. 'useBigInt64',
  64. 'promoteBuffers',
  65. 'promoteLongs',
  66. 'bsonRegExp',
  67. 'enableUtf8Validation',
  68. 'promoteValues',
  69. 'compression',
  70. 'retryWrites'
  71. ];
  72. /** @internal */
  73. export interface DbPrivate {
  74. options?: DbOptions;
  75. readPreference?: ReadPreference;
  76. pkFactory: PkFactory;
  77. readConcern?: ReadConcern;
  78. bsonOptions: BSONSerializeOptions;
  79. writeConcern?: WriteConcern;
  80. namespace: MongoDBNamespace;
  81. }
  82. /** @public */
  83. export interface DbOptions extends BSONSerializeOptions, WriteConcernOptions {
  84. /** If the database authentication is dependent on another databaseName. */
  85. authSource?: string;
  86. /** Force server to assign _id values instead of driver. */
  87. forceServerObjectId?: boolean;
  88. /** The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). */
  89. readPreference?: ReadPreferenceLike;
  90. /** A primary key factory object for generation of custom _id keys. */
  91. pkFactory?: PkFactory;
  92. /** Specify a read concern for the collection. (only MongoDB 3.2 or higher supported) */
  93. readConcern?: ReadConcern;
  94. /** Should retry failed writes */
  95. retryWrites?: boolean;
  96. }
  97. /**
  98. * The **Db** class is a class that represents a MongoDB Database.
  99. * @public
  100. *
  101. * @example
  102. * ```ts
  103. * import { MongoClient } from 'mongodb';
  104. *
  105. * interface Pet {
  106. * name: string;
  107. * kind: 'dog' | 'cat' | 'fish';
  108. * }
  109. *
  110. * const client = new MongoClient('mongodb://localhost:27017');
  111. * const db = client.db();
  112. *
  113. * // Create a collection that validates our union
  114. * await db.createCollection<Pet>('pets', {
  115. * validator: { $expr: { $in: ['$kind', ['dog', 'cat', 'fish']] } }
  116. * })
  117. * ```
  118. */
  119. export class Db {
  120. /** @internal */
  121. s: DbPrivate;
  122. /** @internal */
  123. readonly client: MongoClient;
  124. public static SYSTEM_NAMESPACE_COLLECTION = CONSTANTS.SYSTEM_NAMESPACE_COLLECTION;
  125. public static SYSTEM_INDEX_COLLECTION = CONSTANTS.SYSTEM_INDEX_COLLECTION;
  126. public static SYSTEM_PROFILE_COLLECTION = CONSTANTS.SYSTEM_PROFILE_COLLECTION;
  127. public static SYSTEM_USER_COLLECTION = CONSTANTS.SYSTEM_USER_COLLECTION;
  128. public static SYSTEM_COMMAND_COLLECTION = CONSTANTS.SYSTEM_COMMAND_COLLECTION;
  129. public static SYSTEM_JS_COLLECTION = CONSTANTS.SYSTEM_JS_COLLECTION;
  130. /**
  131. * Creates a new Db instance
  132. *
  133. * @param client - The MongoClient for the database.
  134. * @param databaseName - The name of the database this instance represents.
  135. * @param options - Optional settings for Db construction
  136. */
  137. constructor(client: MongoClient, databaseName: string, options?: DbOptions) {
  138. options = options ?? {};
  139. // Filter the options
  140. options = filterOptions(options, DB_OPTIONS_ALLOW_LIST);
  141. // Ensure we have a valid db name
  142. validateDatabaseName(databaseName);
  143. // Internal state of the db object
  144. this.s = {
  145. // Options
  146. options,
  147. // Unpack read preference
  148. readPreference: ReadPreference.fromOptions(options),
  149. // Merge bson options
  150. bsonOptions: resolveBSONOptions(options, client),
  151. // Set up the primary key factory or fallback to ObjectId
  152. pkFactory: options?.pkFactory ?? DEFAULT_PK_FACTORY,
  153. // ReadConcern
  154. readConcern: ReadConcern.fromOptions(options),
  155. writeConcern: WriteConcern.fromOptions(options),
  156. // Namespace
  157. namespace: new MongoDBNamespace(databaseName)
  158. };
  159. this.client = client;
  160. }
  161. get databaseName(): string {
  162. return this.s.namespace.db;
  163. }
  164. // Options
  165. get options(): DbOptions | undefined {
  166. return this.s.options;
  167. }
  168. /**
  169. * Check if a secondary can be used (because the read preference is *not* set to primary)
  170. */
  171. get secondaryOk(): boolean {
  172. return this.s.readPreference?.preference !== 'primary' || false;
  173. }
  174. get readConcern(): ReadConcern | undefined {
  175. return this.s.readConcern;
  176. }
  177. /**
  178. * The current readPreference of the Db. If not explicitly defined for
  179. * this Db, will be inherited from the parent MongoClient
  180. */
  181. get readPreference(): ReadPreference {
  182. if (this.s.readPreference == null) {
  183. return this.client.readPreference;
  184. }
  185. return this.s.readPreference;
  186. }
  187. get bsonOptions(): BSONSerializeOptions {
  188. return this.s.bsonOptions;
  189. }
  190. // get the write Concern
  191. get writeConcern(): WriteConcern | undefined {
  192. return this.s.writeConcern;
  193. }
  194. get namespace(): string {
  195. return this.s.namespace.toString();
  196. }
  197. /**
  198. * Create a new collection on a server with the specified options. Use this to create capped collections.
  199. * More information about command options available at https://www.mongodb.com/docs/manual/reference/command/create/
  200. *
  201. * @param name - The name of the collection to create
  202. * @param options - Optional settings for the command
  203. */
  204. async createCollection<TSchema extends Document = Document>(
  205. name: string,
  206. options?: CreateCollectionOptions
  207. ): Promise<Collection<TSchema>> {
  208. return executeOperation(
  209. this.client,
  210. new CreateCollectionOperation(this, name, resolveOptions(this, options)) as TODO_NODE_3286
  211. );
  212. }
  213. /**
  214. * Execute a command
  215. *
  216. * @remarks
  217. * This command does not inherit options from the MongoClient.
  218. *
  219. * The driver will ensure the following fields are attached to the command sent to the server:
  220. * - `lsid` - sourced from an implicit session or options.session
  221. * - `$readPreference` - defaults to primary or can be configured by options.readPreference
  222. * - `$db` - sourced from the name of this database
  223. *
  224. * If the client has a serverApi setting:
  225. * - `apiVersion`
  226. * - `apiStrict`
  227. * - `apiDeprecationErrors`
  228. *
  229. * When in a transaction:
  230. * - `readConcern` - sourced from readConcern set on the TransactionOptions
  231. * - `writeConcern` - sourced from writeConcern set on the TransactionOptions
  232. *
  233. * Attaching any of the above fields to the command will have no effect as the driver will overwrite the value.
  234. *
  235. * @param command - The command to run
  236. * @param options - Optional settings for the command
  237. */
  238. async command(command: Document, options?: RunCommandOptions): Promise<Document> {
  239. // Intentionally, we do not inherit options from parent for this operation.
  240. return executeOperation(this.client, new RunCommandOperation(this, command, options));
  241. }
  242. /**
  243. * Execute an aggregation framework pipeline against the database, needs MongoDB \>= 3.6
  244. *
  245. * @param pipeline - An array of aggregation stages to be executed
  246. * @param options - Optional settings for the command
  247. */
  248. aggregate<T extends Document = Document>(
  249. pipeline: Document[] = [],
  250. options?: AggregateOptions
  251. ): AggregationCursor<T> {
  252. return new AggregationCursor(
  253. this.client,
  254. this.s.namespace,
  255. pipeline,
  256. resolveOptions(this, options)
  257. );
  258. }
  259. /** Return the Admin db instance */
  260. admin(): Admin {
  261. return new Admin(this);
  262. }
  263. /**
  264. * Returns a reference to a MongoDB Collection. If it does not exist it will be created implicitly.
  265. *
  266. * @param name - the collection name we wish to access.
  267. * @returns return the new Collection instance
  268. */
  269. collection<TSchema extends Document = Document>(
  270. name: string,
  271. options: CollectionOptions = {}
  272. ): Collection<TSchema> {
  273. if (typeof options === 'function') {
  274. throw new MongoInvalidArgumentError('The callback form of this helper has been removed.');
  275. }
  276. return new Collection<TSchema>(this, name, resolveOptions(this, options));
  277. }
  278. /**
  279. * Get all the db statistics.
  280. *
  281. * @param options - Optional settings for the command
  282. */
  283. async stats(options?: DbStatsOptions): Promise<Document> {
  284. return executeOperation(this.client, new DbStatsOperation(this, resolveOptions(this, options)));
  285. }
  286. /**
  287. * List all collections of this database with optional filter
  288. *
  289. * @param filter - Query to filter collections by
  290. * @param options - Optional settings for the command
  291. */
  292. listCollections(
  293. filter: Document,
  294. options: Exclude<ListCollectionsOptions, 'nameOnly'> & { nameOnly: true }
  295. ): ListCollectionsCursor<Pick<CollectionInfo, 'name' | 'type'>>;
  296. listCollections(
  297. filter: Document,
  298. options: Exclude<ListCollectionsOptions, 'nameOnly'> & { nameOnly: false }
  299. ): ListCollectionsCursor<CollectionInfo>;
  300. listCollections<
  301. T extends Pick<CollectionInfo, 'name' | 'type'> | CollectionInfo =
  302. | Pick<CollectionInfo, 'name' | 'type'>
  303. | CollectionInfo
  304. >(filter?: Document, options?: ListCollectionsOptions): ListCollectionsCursor<T>;
  305. listCollections<
  306. T extends Pick<CollectionInfo, 'name' | 'type'> | CollectionInfo =
  307. | Pick<CollectionInfo, 'name' | 'type'>
  308. | CollectionInfo
  309. >(filter: Document = {}, options: ListCollectionsOptions = {}): ListCollectionsCursor<T> {
  310. return new ListCollectionsCursor<T>(this, filter, resolveOptions(this, options));
  311. }
  312. /**
  313. * Rename a collection.
  314. *
  315. * @remarks
  316. * This operation does not inherit options from the MongoClient.
  317. *
  318. * @param fromCollection - Name of current collection to rename
  319. * @param toCollection - New name of of the collection
  320. * @param options - Optional settings for the command
  321. */
  322. async renameCollection<TSchema extends Document = Document>(
  323. fromCollection: string,
  324. toCollection: string,
  325. options?: RenameOptions
  326. ): Promise<Collection<TSchema>> {
  327. // Intentionally, we do not inherit options from parent for this operation.
  328. return executeOperation(
  329. this.client,
  330. new RenameOperation(
  331. this.collection<TSchema>(fromCollection) as TODO_NODE_3286,
  332. toCollection,
  333. { ...options, new_collection: true, readPreference: ReadPreference.primary }
  334. ) as TODO_NODE_3286
  335. );
  336. }
  337. /**
  338. * Drop a collection from the database, removing it permanently. New accesses will create a new collection.
  339. *
  340. * @param name - Name of collection to drop
  341. * @param options - Optional settings for the command
  342. */
  343. async dropCollection(name: string, options?: DropCollectionOptions): Promise<boolean> {
  344. return executeOperation(
  345. this.client,
  346. new DropCollectionOperation(this, name, resolveOptions(this, options))
  347. );
  348. }
  349. /**
  350. * Drop a database, removing it permanently from the server.
  351. *
  352. * @param options - Optional settings for the command
  353. */
  354. async dropDatabase(options?: DropDatabaseOptions): Promise<boolean> {
  355. return executeOperation(
  356. this.client,
  357. new DropDatabaseOperation(this, resolveOptions(this, options))
  358. );
  359. }
  360. /**
  361. * Fetch all collections for the current db.
  362. *
  363. * @param options - Optional settings for the command
  364. */
  365. async collections(options?: ListCollectionsOptions): Promise<Collection[]> {
  366. return executeOperation(
  367. this.client,
  368. new CollectionsOperation(this, resolveOptions(this, options))
  369. );
  370. }
  371. /**
  372. * Creates an index on the db and collection.
  373. *
  374. * @param name - Name of the collection to create the index on.
  375. * @param indexSpec - Specify the field to index, or an index specification
  376. * @param options - Optional settings for the command
  377. */
  378. async createIndex(
  379. name: string,
  380. indexSpec: IndexSpecification,
  381. options?: CreateIndexesOptions
  382. ): Promise<string> {
  383. return executeOperation(
  384. this.client,
  385. new CreateIndexOperation(this, name, indexSpec, resolveOptions(this, options))
  386. );
  387. }
  388. /**
  389. * Add a user to the database
  390. *
  391. * @param username - The username for the new user
  392. * @param passwordOrOptions - An optional password for the new user, or the options for the command
  393. * @param options - Optional settings for the command
  394. * @deprecated Use the createUser command in `db.command()` instead.
  395. * @see https://www.mongodb.com/docs/manual/reference/command/createUser/
  396. */
  397. async addUser(
  398. username: string,
  399. passwordOrOptions?: string | AddUserOptions,
  400. options?: AddUserOptions
  401. ): Promise<Document> {
  402. options =
  403. options != null && typeof options === 'object'
  404. ? options
  405. : passwordOrOptions != null && typeof passwordOrOptions === 'object'
  406. ? passwordOrOptions
  407. : undefined;
  408. const password = typeof passwordOrOptions === 'string' ? passwordOrOptions : undefined;
  409. return executeOperation(
  410. this.client,
  411. new AddUserOperation(this, username, password, resolveOptions(this, options))
  412. );
  413. }
  414. /**
  415. * Remove a user from a database
  416. *
  417. * @param username - The username to remove
  418. * @param options - Optional settings for the command
  419. */
  420. async removeUser(username: string, options?: RemoveUserOptions): Promise<boolean> {
  421. return executeOperation(
  422. this.client,
  423. new RemoveUserOperation(this, username, resolveOptions(this, options))
  424. );
  425. }
  426. /**
  427. * Set the current profiling level of MongoDB
  428. *
  429. * @param level - The new profiling level (off, slow_only, all).
  430. * @param options - Optional settings for the command
  431. */
  432. async setProfilingLevel(
  433. level: ProfilingLevel,
  434. options?: SetProfilingLevelOptions
  435. ): Promise<ProfilingLevel> {
  436. return executeOperation(
  437. this.client,
  438. new SetProfilingLevelOperation(this, level, resolveOptions(this, options))
  439. );
  440. }
  441. /**
  442. * Retrieve the current profiling Level for MongoDB
  443. *
  444. * @param options - Optional settings for the command
  445. */
  446. async profilingLevel(options?: ProfilingLevelOptions): Promise<string> {
  447. return executeOperation(
  448. this.client,
  449. new ProfilingLevelOperation(this, resolveOptions(this, options))
  450. );
  451. }
  452. /**
  453. * Retrieves this collections index info.
  454. *
  455. * @param name - The name of the collection.
  456. * @param options - Optional settings for the command
  457. */
  458. async indexInformation(name: string, options?: IndexInformationOptions): Promise<Document> {
  459. return executeOperation(
  460. this.client,
  461. new IndexInformationOperation(this, name, resolveOptions(this, options))
  462. );
  463. }
  464. /**
  465. * Create a new Change Stream, watching for new changes (insertions, updates,
  466. * replacements, deletions, and invalidations) in this database. Will ignore all
  467. * changes to system collections.
  468. *
  469. * @remarks
  470. * watch() accepts two generic arguments for distinct use cases:
  471. * - The first is to provide the schema that may be defined for all the collections within this database
  472. * - The second is to override the shape of the change stream document entirely, if it is not provided the type will default to ChangeStreamDocument of the first argument
  473. *
  474. * @param pipeline - An array of {@link https://www.mongodb.com/docs/manual/reference/operator/aggregation-pipeline/|aggregation pipeline stages} through which to pass change stream documents. This allows for filtering (using $match) and manipulating the change stream documents.
  475. * @param options - Optional settings for the command
  476. * @typeParam TSchema - Type of the data being detected by the change stream
  477. * @typeParam TChange - Type of the whole change stream document emitted
  478. */
  479. watch<
  480. TSchema extends Document = Document,
  481. TChange extends Document = ChangeStreamDocument<TSchema>
  482. >(pipeline: Document[] = [], options: ChangeStreamOptions = {}): ChangeStream<TSchema, TChange> {
  483. // Allow optionally not specifying a pipeline
  484. if (!Array.isArray(pipeline)) {
  485. options = pipeline;
  486. pipeline = [];
  487. }
  488. return new ChangeStream<TSchema, TChange>(this, pipeline, resolveOptions(this, options));
  489. }
  490. /**
  491. * A low level cursor API providing basic driver functionality:
  492. * - ClientSession management
  493. * - ReadPreference for server selection
  494. * - Running getMores automatically when a local batch is exhausted
  495. *
  496. * @param command - The command that will start a cursor on the server.
  497. * @param options - Configurations for running the command, bson options will apply to getMores
  498. */
  499. runCursorCommand(command: Document, options?: RunCursorCommandOptions): RunCommandCursor {
  500. return new RunCommandCursor(this, command, options);
  501. }
  502. }
  503. // TODO(NODE-3484): Refactor into MongoDBNamespace
  504. // Validate the database name
  505. function validateDatabaseName(databaseName: string) {
  506. if (typeof databaseName !== 'string')
  507. throw new MongoInvalidArgumentError('Database name must be a string');
  508. if (databaseName.length === 0)
  509. throw new MongoInvalidArgumentError('Database name cannot be the empty string');
  510. if (databaseName === '$external') return;
  511. const invalidChars = [' ', '.', '$', '/', '\\'];
  512. for (let i = 0; i < invalidChars.length; i++) {
  513. if (databaseName.indexOf(invalidChars[i]) !== -1)
  514. throw new MongoAPIError(`database names cannot contain the character '${invalidChars[i]}'`);
  515. }
  516. }