Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions packages/mongodb-ns/src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,28 @@ describe('ns', function () {
assert.equal(ns('abc.').database, 'abc');
});

describe('shared classification regexps', function () {
it('keeps an implied lastIndex of 0 between parses', function () {
// The RegExps were moved up to the module scope and are shared across invocations
// if one were to add 'g' or 'y' they would become stateful
// these tests will fail if the RegExps become stateful
assert(ns('__mdb_internal_a.x').isInternal());
assert(ns('__mdb_internal_b.x').isInternal());

assert(ns('a.system.javascript').isSystem());
assert(ns('b.system.typescript').isSystem());

assert(ns('c.local.oplog.rs').isOplog());
assert(ns('d.local.oplog.$main').isOplog());

assert.equal(ns('foo').validDatabaseName, true);
assert.equal(ns('bar').validDatabaseName, true);

assert(ns('p.mycol').validCollectionName);
assert(ns('q.yourcol').validCollectionName);
});
});

describe('sorting', function () {
it('should sort them', function () {
const names = [
Expand Down
106 changes: 98 additions & 8 deletions packages/mongodb-ns/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,111 @@
/**
* A MongoDB namespace (the `database.collection` string) and the
* classification flags derived from it.
*/
type NS = {
/**
* The full namespace string, e.g. `'mydb.mycollection'`.
*/
ns: string;
/**
* Index of the first `.` in `ns`, or `-1` when there is no dot.
*/
dotIndex: number;
/**
* Everything before the first `.` in `ns`
* or the whole string when there is no dot.
*/
database: string;
/**
* Everything after the first `.` in `ns`, or `''` when there is no dot.
*/
collection: string;
/**
* @see isSystem
*/
system: boolean;
/**
* Whether this is a system-generated collection: the collection name is a
* `system.*` collection (anything beginning with `system.` except
* `system.profile` itself) or an `enxcol_.` queryable-encryption
* collection.
*/
isSystem(): boolean;
/**
* @see isOplog
*/
oplog: boolean;
/**
* Whether the namespace is one of the two oplog namespaces,
* `local.oplog.$main` or `local.oplog.rs`.
*/
isOplog(): boolean;
/**
* @see isCommand
*/
command: boolean;
/**
* Whether the collection is the command collection `$cmd` or begins with
* `$cmd.sys`.
*/
isCommand(): boolean;
/**
* @see isSpecial
*/
special: boolean;
/**
* Whether the namespace is not an ordinary database/collection pair: set
* when any of `oplog`, `command`, `system`, `internal` is true or the
* database is `config`.
*/
isSpecial(): boolean;
/**
* One level looser than `special`: true when `special` is true, or the
* database is `local` or `admin`. `NS.sort` uses it to keep these
* namespaces at the end of a sorted list.
*/
specialish: boolean;
/**
* @see isNormal
*/
normal: boolean;
/**
* Whether the namespace is an oplog, or when `ns` contains no `$` character
* at all. (Despite the name, this includes the oplog.)
*/
isNormal(): boolean;
/**
* @see isInternal
*/
internal: boolean;
/**
* Whether the database is an internal database - its name matches
* `/^__mdb_internal_\w/`. See the MongoDB Atlas "internal databases" docs.
*
* @see https://www.mongodb.com/docs/atlas/reference/internal-database/#internal-databases
*/
isInternal(): boolean;
/**
* True when the database is a valid database name, i.e. it contains no
* backslash, `/`, `"`, `.` or space, and is at most
* `NS.MAX_DATABASE_NAME_LENGTH` (128) characters long.
*/
validDatabaseName: boolean;
/**
* True when the collection name is non-empty and either the namespace is an
* oplog or the name contains no NUL byte and no `$`.
*/
validCollectionName: boolean;
/**
* @deprecated An approximate equivalent to Java's hashCode that does not work for db names longer than 30 characters.
*
* Hash of the database portion of `ns`, computed in the constructor from
* the characters up to (and excluding) the first `.`.
*/
databaseHash: number;
/** The namespace string itself (`ns`). */
toString(): string;
// Assigned in the constructor, but will always be undefined
/** Always `undefined` - reserved, never implemented. */
isConf(): undefined;
};

Expand All @@ -32,6 +117,12 @@ type NSConstructor = {
sort(namespaces: (string | NS)[]): typeof namespaces;
};

const INTERNAL_DATABASE_REGEXP = /^__mdb_internal_\w/;
const SYSTEM_COLLECTION_REGEXP = /^(?:system(?!\.profile$).*|enxcol_)\./;
const OPLOG_REGEXP = /local\.oplog\.(\$main|rs)/;
const VALID_DATABASE_NAME_REGEXP = /^[^\\/". ]*$/;
const VALID_COLLECTION_NAME_REGEXP = /^[^\0$]*$/;

// eslint-disable-next-line complexity
const NS: NSConstructor = function (this: NS, ns: string | NS): NS {
ns = ns.toString();
Expand All @@ -49,12 +140,11 @@ const NS: NSConstructor = function (this: NS, ns: string | NS): NS {
this.collection = ns.slice(this.dotIndex + 1);
}

// https://www.mongodb.com/docs/atlas/reference/internal-database/#internal-databases
this.internal = /^__mdb_internal_\w/.test(this.database);
this.internal = INTERNAL_DATABASE_REGEXP.test(this.database);

this.system = /^(?:system(?!\.profile$).*|enxcol_)\./.test(this.collection);
this.system = SYSTEM_COLLECTION_REGEXP.test(this.collection);

this.oplog = /local\.oplog\.(\$main|rs)/.test(ns);
this.oplog = OPLOG_REGEXP.test(ns);

this.command =
this.collection === '$cmd' || this.collection.indexOf('$cmd.sys') === 0;
Expand All @@ -66,7 +156,7 @@ const NS: NSConstructor = function (this: NS, ns: string | NS): NS {
this.internal;

this.specialish =
this.special || ['local', 'admin'].indexOf(this.database) > -1;
this.special || this.database === 'local' || this.database === 'admin';

this.normal = this.oplog || this.ns.indexOf('$') === -1;

Expand All @@ -75,11 +165,11 @@ const NS: NSConstructor = function (this: NS, ns: string | NS): NS {
* `*<>:|?`
*/
this.validDatabaseName =
new RegExp('^[^\\\\/". ]*$').test(this.database) &&
VALID_DATABASE_NAME_REGEXP.test(this.database) &&
this.database.length <= NS.MAX_DATABASE_NAME_LENGTH;
this.validCollectionName =
this.collection.length > 0 &&
(this.oplog || /^[^\0$]*$/.test(this.collection));
(this.oplog || VALID_COLLECTION_NAME_REGEXP.test(this.collection));

this.databaseHash = 7;
this.ns.split('').every((c, i) => {
Expand Down