Repository: Automattic/mongoose (JavaScript)

Issue:
Bug: Mongoose connection ID is not unique
### Prerequisites

- [X] I have written a descriptive issue title
- [X] I have searched existing issues to ensure the bug has not already been reported


### Mongoose version

All

### Node.js version

All

### MongoDB server version

All

### Typescript version (if applicable)

_No response_

### Description

As discovered in <https://github.com/Automattic/mongoose/issues/12965#issuecomment-1410896781>, `connection.id` is not unique due to `.length` being used to determine its value.  As connections are created and destroyed, `id` properties get re-used.

### Steps to Reproduce

```sh
const mongoose = require('mongoose');
mongoose.createConnection()
mongoose.createConnection()
mongoose.connections[0].destroy()
mongoose.createConnection()
mongoose.createConnection()
mongoose.createConnection()
mongoose.connections.map(c => c.id) // outputs: [ 1, 2, 3, 3, 4 ]
```

### Expected Behavior

_No response_


Discussion:
cc @vkarpov15 
@titanism @vkarpov15 What impact would this bug have had on the application?
@manjitkumar none. Connection `id` property is purely for logging and debugging purposes. Mongoose runtime behavior does not rely on the connection's `id`.



Patch:
diff --git a/lib/connection.js b/lib/connection.js
index 9339f138a05..1a3e817eaeb 100644
--- a/lib/connection.js
+++ b/lib/connection.js
@@ -72,7 +72,7 @@ function Connection(base) {
   if (typeof base === 'undefined' || !base.connections.length) {
     this.id = 0;
   } else {
-    this.id = base.connections.length;
+    this.id = base.nextConnectionId;
   }
   this._queue = [];
 }
diff --git a/lib/index.js b/lib/index.js
index 2109d3eec6d..0d1dd18f4b2 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -61,6 +61,7 @@ const objectIdHexRegexp = /^[0-9A-Fa-f]{24}$/;
  */
 function Mongoose(options) {
   this.connections = [];
+  this.nextConnectionId = 0;
   this.models = {};
   this.events = new EventEmitter();
   this.__driver = driver.get();
@@ -345,6 +346,7 @@ Mongoose.prototype.createConnection = function(uri, options, callback) {
     options = null;
   }
   _mongoose.connections.push(conn);
+  _mongoose.nextConnectionId++;
   _mongoose.events.emit('createConnection', conn);
 
   if (arguments.length > 0) {
@@ -789,6 +791,27 @@ Mongoose.prototype.__defineSetter__('connection', function(v) {
 
 Mongoose.prototype.connections;
 
+/**
+ * An integer containing the value of the next connection id. Calling
+ * [`createConnection()`](#mongoose_Mongoose-createConnection) increments
+ * this value.
+ *
+ * #### Example:
+ *
+ *     const mongoose = require('mongoose');
+ *     mongoose.createConnection(); // id `0`, `nextConnectionId` becomes `1`
+ *     mongoose.createConnection(); // id `1`, `nextConnectionId` becomes `2`
+ *     mongoose.connections[0].destroy() // Removes connection with id `0`
+ *     mongoose.createConnection(); // id `2`, `nextConnectionId` becomes `3`
+ *
+ * @memberOf Mongoose
+ * @instance
+ * @property {Number} nextConnectionId
+ * @api private
+ */
+
+Mongoose.prototype.nextConnectionId;
+
 /**
  * The Mongoose Aggregate constructor
  *


Test patch:
diff --git a/test/connection.test.js b/test/connection.test.js
index 3d6ef8f795a..d27c7879605 100644
--- a/test/connection.test.js
+++ b/test/connection.test.js
@@ -1514,4 +1514,16 @@ describe('connections:', function() {
 
     await m.disconnect();
   });
+
+  it('should create connections with unique IDs also if one has been destroyed (gh-12966)', function() {
+    const m = new mongoose.Mongoose();
+    m.createConnection();
+    m.createConnection();
+    m.connections[0].destroy();
+    m.createConnection();
+    m.createConnection();
+    m.createConnection();
+    const connectionIds = m.connections.map(c => c.id);
+    assert.deepEqual(connectionIds, [1, 2, 3, 4, 5]);
+  });
 });