{"action":"created","comment":{"url":"https://api.github.com/repos/googleapis/nodejs-spanner/pulls/comments/196572217","pull_request_review_id":130152240,"id":196572217,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDE5NjU3MjIxNw==","diff_hunk":"@@ -16,2022 +16,1575 @@\n \n 'use strict';\n \n-var assert = require('assert');\n-var common = require('@google-cloud/common');\n-var delay = require('delay');\n-var events = require('events');\n-var extend = require('extend');\n-var PQueue = require('p-queue');\n-var proxyquire = require('proxyquire');\n-var stackTrace = require('stack-trace');\n-var through = require('through2');\n-var timeSpan = require('time-span');\n-\n-var pQueueOverride = null;\n-function FakePQueue(options) {\n-  return new (pQueueOverride || PQueue)(options);\n-}\n-\n-var fakeStackTrace = extend({}, stackTrace);\n+const assert = require('assert');\n+const common = require('@google-cloud/common');\n+const extend = require('extend');\n+const through = require('through2');\n \n describe('SessionPool', function() {\n-  var SessionPool;\n-  var sessionPool;\n+  let SessionPool;\n \n-  var DATABASE = {\n+  let DATABASE = {\n     request: common.util.noop,\n     requestStream: common.util.noop,\n   };\n \n   before(function() {\n-    SessionPool = proxyquire('../src/session-pool.js', {\n-      'p-queue': FakePQueue,\n-      'stack-trace': fakeStackTrace,\n-    });\n-  });\n-\n-  beforeEach(function() {\n-    sessionPool = new SessionPool(DATABASE);\n-    sessionPool.onClose_ = new Promise(function() {});\n+    SessionPool = require('../src/session-pool.js');\n   });\n \n-  afterEach(function() {\n-    pQueueOverride = null;\n-    fakeStackTrace.get = stackTrace.get;\n-  });\n+  describe('tests with no min sessions sessionPool before hook', function() {\n+    let sessionPool;\n+    // Because we create a sessionPool here for every test tests which want to open\n+    // their own pool must first close this one and wait on its returned promise.\n+    // Otherwise the tests will have resource leaks.\n+    beforeEach(function() {\n+      sessionPool = new SessionPool(DATABASE, {\n+        maxReads: 10,\n+        maxWrites: 5,\n+        acquireTimeout: 50,\n+      });\n+    });\n \n-  describe('instantiation', function() {\n-    it('should localize the database instance', function() {\n-      assert.strictEqual(sessionPool.database, DATABASE);\n+    afterEach(function() {\n+      clearTimeout(sessionPool.pingTimeoutHandle);\n+      if (sessionPool.isOpen) {\n+        return sessionPool.close();\n+      }\n     });\n \n-    describe('options', function() {\n-      it('should apply defaults', function() {\n-        assert.strictEqual(sessionPool.options.acquireTimeout, Infinity);\n-        assert.strictEqual(sessionPool.options.concurrency, 10);\n-        assert.strictEqual(sessionPool.options.fail, false);\n-        assert.strictEqual(sessionPool.options.idlesAfter, 10);\n-        assert.strictEqual(sessionPool.options.keepAlive, 50);\n-        assert.strictEqual(sessionPool.options.max, 100);\n-        assert.strictEqual(sessionPool.options.maxIdle, 1);\n-        assert.strictEqual(sessionPool.options.min, 0);\n-        assert.strictEqual(sessionPool.options.writes, 0);\n+    describe('instantiation', function() {\n+      it('should localize the database instance', function() {\n+        assert.strictEqual(sessionPool.database, DATABASE);\n       });\n \n-      it('should not override user options', function() {\n-        sessionPool = new SessionPool(DATABASE, {acquireTimeout: 0});\n-        assert.strictEqual(sessionPool.options.acquireTimeout, 0);\n-      });\n+      describe('options', function() {\n+        it('should apply defaults to non-modified options', function() {\n+          assert.strictEqual(sessionPool.options.acquireTimeout, 50);\n+          assert.strictEqual(sessionPool.options.concurrency, 10);\n+          assert.strictEqual(sessionPool.options.maxWait, 50);\n+          assert.strictEqual(sessionPool.options.idlesAfter, 50);\n+          assert.strictEqual(sessionPool.options.maxReads, 10);\n+          assert.strictEqual(sessionPool.options.maxWrites, 5);\n+          assert.strictEqual(sessionPool.options.minReads, 0);\n+          assert.strictEqual(sessionPool.options.minWrites, 0);\n+        });\n \n-      it('should throw when writes is greater than 1', function() {\n-        assert.throws(function() {\n-          return new SessionPool(DATABASE, {writes: 50});\n-        }, /Write percentage should be represented as a float between 0\\.0 and 1\\.0\\./);\n-      });\n-    });\n+        it('should not override user options', function() {\n+          return sessionPool.close().then(function() {\n+            sessionPool = new SessionPool(DATABASE, {acquireTimeout: 1});\n+            assert.strictEqual(sessionPool.options.acquireTimeout, 1);\n+            assert.strictEqual(\n+              sessionPool.readPool._config.acquireTimeoutMillis,\n+              1\n+            );\n+            assert.strictEqual(\n+              sessionPool.writePool._config.acquireTimeoutMillis,\n+              1\n+            );\n+          });\n+        });\n \n-    it('should set isOpen to false', function() {\n-      assert.strictEqual(sessionPool.isOpen, false);\n-    });\n+        it('should apply defaults', function() {\n+          return sessionPool.close().then(function() {\n+            sessionPool = new SessionPool(DATABASE);\n+            assert.strictEqual(sessionPool.options.acquireTimeout, Infinity);\n+            assert.strictEqual(sessionPool.options.concurrency, 10);\n+            assert.strictEqual(sessionPool.options.maxWait, 50);\n+            assert.strictEqual(sessionPool.options.idlesAfter, 50);\n+            assert.strictEqual(sessionPool.options.max, 100);\n+            assert.strictEqual(sessionPool.options.writes, 0);\n+            assert.strictEqual(sessionPool.options.min, 0);\n+            assert.strictEqual(sessionPool.options.maxReads, 100);\n+            assert.strictEqual(sessionPool.options.maxWrites, 0);\n+            assert.strictEqual(sessionPool.options.minReads, 0);\n+            assert.strictEqual(sessionPool.options.minWrites, 0);\n+          });\n+        });\n \n-    it('should localize database request functions', function() {\n-      assert.strictEqual(sessionPool.request_, DATABASE.request);\n-      assert.strictEqual(sessionPool.requestStream_, DATABASE.requestStream);\n-    });\n+        it('should override DEFAULT for writes', function() {\n+          return sessionPool.close().then(function() {\n+            sessionPool = new SessionPool(DATABASE, {writes: 0.5});\n+            assert.strictEqual(sessionPool.options.acquireTimeout, Infinity);\n+            assert.strictEqual(sessionPool.options.concurrency, 10);\n+            assert.strictEqual(sessionPool.options.maxWait, 50);\n+            assert.strictEqual(sessionPool.options.idlesAfter, 50);\n+            assert.strictEqual(sessionPool.options.max, 100);\n+            assert.strictEqual(sessionPool.options.writes, 0.5);\n+            assert.strictEqual(sessionPool.options.min, 0);\n+            assert.strictEqual(sessionPool.options.maxReads, 50);\n+            assert.strictEqual(sessionPool.options.maxWrites, 50);\n+            assert.strictEqual(sessionPool.options.minReads, 0);\n+            assert.strictEqual(sessionPool.options.minWrites, 0);\n+          });\n+        });\n \n-    it('should set pendingCreates to 0', function() {\n-      assert.strictEqual(sessionPool.pendingCreates_, 0);\n-    });\n+        it('should override DEFAULT for maxWrites', function() {\n+          return sessionPool.close().then(function() {\n+            sessionPool = new SessionPool(DATABASE, {\n+              maxWrites: 21,\n+              writes: 0.5,\n+            });\n+            assert.strictEqual(sessionPool.options.acquireTimeout, Infinity);\n+            assert.strictEqual(sessionPool.options.concurrency, 10);\n+            assert.strictEqual(sessionPool.options.maxWait, 50);\n+            assert.strictEqual(sessionPool.options.idlesAfter, 50);\n+            assert.strictEqual(sessionPool.options.max, 100);\n+            assert.strictEqual(sessionPool.options.writes, 0.5);\n+            assert.strictEqual(sessionPool.options.min, 0);\n+            assert.strictEqual(sessionPool.options.maxReads, 79);\n+            assert.strictEqual(sessionPool.options.maxWrites, 21);\n+            assert.strictEqual(sessionPool.options.minReads, 0);\n+            assert.strictEqual(sessionPool.options.minWrites, 0);\n+          });\n+        });\n \n-    it('should create arrays for available and borrowed sessions', function() {\n-      assert.deepEqual(sessionPool.reads_, []);\n-      assert.deepEqual(sessionPool.writes_, []);\n-      assert.deepEqual(sessionPool.borrowed_, []);\n-    });\n+        it('should override DEFAULT for maxReads', function() {\n+          return sessionPool.close().then(function() {\n+            sessionPool = new SessionPool(DATABASE, {\n+              writes: 0.5,\n+              maxReads: 40,\n+            });\n+            assert.strictEqual(sessionPool.options.acquireTimeout, Infinity);\n+            assert.strictEqual(sessionPool.options.concurrency, 10);\n+            assert.strictEqual(sessionPool.options.maxWait, 50);\n+            assert.strictEqual(sessionPool.options.idlesAfter, 50);\n+            assert.strictEqual(sessionPool.options.max, 100);\n+            assert.strictEqual(sessionPool.options.writes, 0.5);\n+            assert.strictEqual(sessionPool.options.min, 0);\n+            assert.strictEqual(sessionPool.options.maxReads, 40);\n+            assert.strictEqual(sessionPool.options.maxWrites, 60);\n+            assert.strictEqual(sessionPool.options.minReads, 0);\n+            assert.strictEqual(sessionPool.options.minWrites, 0);\n+          });\n+        });\n \n-    it('should create an acquire queue with 1 concurrency', function() {\n-      pQueueOverride = function(options) {\n-        return options;\n-      };\n+        it('should override DEFAULT for minReads and minWrites', function() {\n+          return sessionPool.close().then(function() {\n+            sessionPool = new SessionPool(DATABASE, {\n+              writes: 0.5,\n+              maxReads: 40,\n+              minReads: 41,\n+              minWrites: 61,\n+              min: 70,\n+            });\n+            assert.strictEqual(sessionPool.options.acquireTimeout, Infinity);\n+            assert.strictEqual(sessionPool.options.concurrency, 10);\n+            assert.strictEqual(sessionPool.options.maxWait, 50);\n+            assert.strictEqual(sessionPool.options.idlesAfter, 50);\n+            assert.strictEqual(sessionPool.options.max, 100);\n+            assert.strictEqual(sessionPool.options.writes, 0.5);\n+            assert.strictEqual(sessionPool.options.min, 70);\n+            assert.strictEqual(sessionPool.options.maxReads, 40);\n+            assert.strictEqual(sessionPool.options.maxWrites, 60);\n+            assert.strictEqual(sessionPool.options.minReads, 40);\n+            assert.strictEqual(sessionPool.options.minWrites, 60);\n+          });\n+        });\n+\n+        it('should override DEFAULT for minReads and minWrites', function() {\n+          return sessionPool.close().then(function() {\n+            sessionPool = new SessionPool(DATABASE, {\n+              writes: 0.5,\n+              maxReads: 40,\n+              min: 70,\n+            });\n+            assert.strictEqual(sessionPool.options.acquireTimeout, Infinity);\n+            assert.strictEqual(sessionPool.options.concurrency, 10);\n+            assert.strictEqual(sessionPool.options.maxWait, 50);\n+            assert.strictEqual(sessionPool.options.idlesAfter, 50);\n+            assert.strictEqual(sessionPool.options.max, 100);\n+            assert.strictEqual(sessionPool.options.writes, 0.5);\n+            assert.strictEqual(sessionPool.options.min, 70);\n+            assert.strictEqual(sessionPool.options.maxReads, 40);\n+            assert.strictEqual(sessionPool.options.maxWrites, 60);\n+            assert.strictEqual(sessionPool.options.minReads, 40);\n+            assert.strictEqual(sessionPool.options.minWrites, 60);\n+          });\n+        });\n \n-      sessionPool = new SessionPool(DATABASE);\n-      assert.deepEqual(sessionPool.acquireQueue_, {\n-        concurrency: 1,\n+        it('should override DEFAULT for minReads and minWrites', function() {\n+          return sessionPool.close().then(function() {\n+            sessionPool = new SessionPool(DATABASE, {\n+              writes: 0.5,\n+              maxReads: 40,\n+              min: 20,\n+            });\n+            assert.strictEqual(sessionPool.options.acquireTimeout, Infinity);\n+            assert.strictEqual(sessionPool.options.concurrency, 10);\n+            assert.strictEqual(sessionPool.options.maxWait, 50);\n+            assert.strictEqual(sessionPool.options.idlesAfter, 50);\n+            assert.strictEqual(sessionPool.options.max, 100);\n+            assert.strictEqual(sessionPool.options.writes, 0.5);\n+            assert.strictEqual(sessionPool.options.min, 20);\n+            assert.strictEqual(sessionPool.options.maxReads, 40);\n+            assert.strictEqual(sessionPool.options.maxWrites, 60);\n+            assert.strictEqual(sessionPool.options.minReads, 20);\n+            assert.strictEqual(sessionPool.options.minWrites, 20);\n+          });\n+        });\n+\n+        it('should throw when writes is greater than 1', function() {\n+          assert.throws(function() {\n+            return new SessionPool(DATABASE, {writes: 50});\n+          }, /Write percentage should be represented as a float between 0\\.0 and 1\\.0\\./);\n+        });\n       });\n-    });\n \n-    it('should create a request queue', function() {\n-      var poolOptions = {\n-        concurrency: 11,\n-      };\n+      it('should set isOpen to false', function() {\n+        assert.strictEqual(sessionPool.isOpen, false);\n+      });\n \n-      pQueueOverride = function(options) {\n-        return options;\n-      };\n+      it('should localize database request functions', function() {\n+        assert.strictEqual(sessionPool.request_, DATABASE.request);\n+        assert.strictEqual(sessionPool.requestStream_, DATABASE.requestStream);\n+      });\n \n-      sessionPool = new SessionPool(DATABASE, poolOptions);\n-      assert.deepEqual(sessionPool.requestQueue_, {\n-        concurrency: poolOptions.concurrency,\n+      it('should set read and write pool sizes to 0', function() {\n+        assert.strictEqual(sessionPool.getStats().readPool.size, 0);\n+        assert.strictEqual(sessionPool.getStats().writePool.size, 0);\n       });\n-    });\n \n-    it('should create handles for intervals', function() {\n-      assert.strictEqual(sessionPool.evictHandle_, null);\n-      assert.strictEqual(sessionPool.pingHandle_, null);\n-    });\n+      it('read and write pools should be initialized with the parameters passed in', function() {\n+        const readPoolOptions = sessionPool.readPool._config;\n+        const writePoolOptions = sessionPool.writePool._config;\n+        assert.strictEqual(readPoolOptions.fifo, true);\n+        assert.strictEqual(readPoolOptions.priorityRange, 1);\n+        assert.strictEqual(readPoolOptions.testOnBorrow, true);\n+        assert.strictEqual(readPoolOptions.testOnReturn, false);\n+        assert.strictEqual(readPoolOptions.autostart, false);\n+        assert.strictEqual(readPoolOptions.maxWaitingClients, 50);\n+        assert.strictEqual(readPoolOptions.max, 10);\n+        assert.strictEqual(readPoolOptions.min, 0);\n+        assert.strictEqual(readPoolOptions.evictionRunIntervalMillis, 3000000);\n+        assert.strictEqual(readPoolOptions.numTestsPerEvictionRun, 3);\n+        assert.strictEqual(readPoolOptions.softIdleTimeoutMillis, -1);\n+        assert.strictEqual(readPoolOptions.idleTimeoutMillis, 3000000);\n+        assert.strictEqual(writePoolOptions.fifo, true);\n+        assert.strictEqual(writePoolOptions.priorityRange, 1);\n+        assert.strictEqual(writePoolOptions.testOnBorrow, true);\n+        assert.strictEqual(writePoolOptions.testOnReturn, false);\n+        assert.strictEqual(writePoolOptions.autostart, false);\n+        assert.strictEqual(writePoolOptions.maxWaitingClients, 50);\n+        assert.strictEqual(writePoolOptions.max, 5);\n+        assert.strictEqual(writePoolOptions.min, 0);\n+        assert.strictEqual(writePoolOptions.evictionRunIntervalMillis, 3000000);\n+        assert.strictEqual(writePoolOptions.numTestsPerEvictionRun, 3);\n+        assert.strictEqual(writePoolOptions.softIdleTimeoutMillis, -1);\n+        assert.strictEqual(writePoolOptions.idleTimeoutMillis, 3000000);\n+      });\n+\n+      it('should create a request queue', function() {\n+        const poolOptions = {\n+          concurrency: 11,\n+        };\n \n-    it('should inherit from EventEmitter', function() {\n-      assert(sessionPool instanceof events.EventEmitter);\n+        return sessionPool.close().then(function() {\n+          sessionPool = new SessionPool(DATABASE, poolOptions);\n+          assert.strictEqual(sessionPool.requestQueue_._concurrency, 11);\n+        });\n+      });\n     });\n-  });\n \n-  describe('formatTrace_', function() {\n-    var fakeFileName = 'path/to/file.js';\n-    var fakeLineNumber = '99';\n-    var fakeColumnNumber = '13';\n-    var file = `${fakeFileName}:${fakeLineNumber}:${fakeColumnNumber}`;\n+    describe('session_', function() {\n+      it('should return a session object', function() {\n+        const fakeSession = {};\n \n-    var fakeFunction;\n-    var fakeMethod;\n+        DATABASE.session_ = function() {\n+          return fakeSession;\n+        };\n \n-    var fakeTrace = [\n-      {},\n-      {},\n-      {\n-        getFunctionName: function() {\n-          return fakeFunction;\n-        },\n-        getMethodName: function() {\n-          return fakeMethod;\n-        },\n-        getFileName: function() {\n-          return fakeFileName;\n-        },\n-        getLineNumber: function() {\n-          return fakeLineNumber;\n-        },\n-        getColumnNumber: function() {\n-          return fakeColumnNumber;\n-        },\n-      },\n-    ];\n+        const session = sessionPool.session_();\n \n-    it('should return a trace with the method name', function() {\n-      fakeMethod = 'MyClass.myMethod';\n+        assert.strictEqual(session, fakeSession);\n+        assert(isAround(session.lastUsed, Date.now()));\n+      });\n+    });\n \n-      var expected = `Session leak detected!\\n    at ${fakeMethod} (${file})`;\n-      var actual = SessionPool.formatTrace_(fakeTrace);\n+    describe('createReadSession', function() {\n+      it('should create a read only session', function() {\n+        let created = false;\n \n-      assert.strictEqual(expected, actual);\n-    });\n+        const fakeSession = {\n+          create: function() {\n+            created = true;\n+            return Promise.resolve();\n+          },\n+        };\n+        sessionPool.session_ = function() {\n+          return fakeSession;\n+        };\n+        return sessionPool.createReadSession().then(function(session) {\n+          assert.strictEqual(session, fakeSession);\n+          assert.strictEqual(session.type, 'readonly');\n+          assert.strictEqual(created, true);\n+        });\n+      });\n \n-    it('should return a trace with the function name', function() {\n-      fakeFunction = 'myFunction';\n+      it('should fail in creating a read only session', function() {\n+        let deleteCalled = false;\n+        const fakeSession = {\n+          create: () => Promise.reject('Error'),\n+          delete: () => {\n+            deleteCalled = true;\n+            return Promise.resolve();\n+          },\n+        };\n \n-      var expected = `Session leak detected!\\n    at ${fakeFunction} (${file})`;\n-      var actual = SessionPool.formatTrace_(fakeTrace);\n+        sessionPool.session_ = function() {\n+          return fakeSession;\n+        };\n \n-      assert.strictEqual(expected, actual);\n+        return sessionPool.createReadSession().catch(error => {\n+          assert.strictEqual(error, 'Error');\n+          assert.strictEqual(deleteCalled, true);\n+        });\n+      });\n     });\n-  });\n \n-  describe('available', function() {\n-    it('should return the number of available sessions', function() {\n-      sessionPool.reads_ = [{}];\n-      sessionPool.writes_ = [{}, {}];\n+    describe('createWriteSession_', function() {\n+      it('should create a write session', function() {\n+        const fakeSession = {\n+          create: () => Promise.resolve(),\n+        };\n \n-      assert.strictEqual(sessionPool.available(), 3);\n-    });\n-  });\n+        sessionPool.session_ = () => fakeSession;\n \n-  describe('borrowed', function() {\n-    it('should return the number of borrowed sessions', function() {\n-      sessionPool.borrowed_ = [{}, {}];\n+        sessionPool.createTransaction_ = function(session) {\n+          assert.strictEqual(session, fakeSession);\n+          return Promise.resolve();\n+        };\n \n-      assert.strictEqual(sessionPool.borrowed(), 2);\n-    });\n-  });\n+        return sessionPool.createWriteSession().then(function(session) {\n+          assert.strictEqual(session, fakeSession);\n+          assert.strictEqual(session.type, 'readwrite');\n+        });\n+      });\n \n-  describe('close', function() {\n-    it('should set isOpen to false', function() {\n-      sessionPool.isOpen = true;\n-      sessionPool.close();\n-      assert.strictEqual(sessionPool.isOpen, false);\n-    });\n+      it('should fail in creating a read/write session', function() {\n+        let deleteCalled = false;\n+        const fakeSession = {\n+          create: () => Promise.reject('Error'),\n+          delete: () => {\n+            deleteCalled = true;\n+            return Promise.resolve();\n+          },\n+        };\n+\n+        sessionPool.session_ = function() {\n+          return fakeSession;\n+        };\n \n-    it('should emit a close event', function(done) {\n-      sessionPool.on('close', done);\n-      sessionPool.close();\n+        return sessionPool.createWriteSession().catch(error => {\n+          assert.strictEqual(error, 'Error');\n+          assert.strictEqual(deleteCalled, true);\n+        });\n+      });\n     });\n \n-    it('should emit an empty event', function(done) {\n-      sessionPool.on('empty', done);\n-      sessionPool.close();\n+    describe('open', function() {\n+      it('should set isOpen to true', function() {\n+        return sessionPool.close().then(function() {\n+          sessionPool = new SessionPool(DATABASE, {minReads: 4, minWrites: 4});\n+          sessionPool.session_ = function() {\n+            return new Object({\n+              create: () => Promise.resolve(),\n+              delete: () => Promise.resolve(),\n+            });\n+          };\n+          sessionPool.open();\n+          assert.strictEqual(sessionPool.isOpen, true);\n+          assert.strictEqual(sessionPool.getStats().readPool.size, 4);\n+          assert.strictEqual(sessionPool.getStats().writePool.size, 0);\n+          clearTimeout(sessionPool.pingTimeoutHandle);\n+          return sessionPool.close();\n+        });\n+      });\n     });\n \n-    it('should call stopHouseKeeping_', function(done) {\n-      sessionPool.stopHouseKeeping_ = done;\n-      sessionPool.close();\n+    describe('close', function() {\n+      it('should destroy all sessions', function() {\n+        return sessionPool.close().then(function() {\n+          sessionPool = new SessionPool(DATABASE, {minReads: 4, minWrites: 4});\n+          sessionPool.createTransaction_ = () => Promise.resolve();\n+          sessionPool.session_ = function() {\n+            return new Object({\n+              create: () => Promise.resolve(),\n+              delete: () => Promise.resolve(),\n+            });\n+          };\n+          sessionPool.open();\n+          assert.strictEqual(sessionPool.isOpen, true);\n+          assert.strictEqual(sessionPool.getStats().readPool.size, 4);\n+          assert.strictEqual(sessionPool.getStats().writePool.size, 0);\n+          clearTimeout(sessionPool.pingTimeoutHandle);\n+          return sessionPool.close().then(() => {\n+            assert.strictEqual(sessionPool.isOpen, false);\n+            assert.strictEqual(sessionPool.getStats().readPool.size, 0);\n+            assert.strictEqual(sessionPool.getStats().writePool.size, 0);\n+          });\n+        });\n+      });\n     });\n \n-    it('should destroy all sessions', function() {\n-      var fakeReads = [{}, {}];\n-      var fakeWrites = [{}];\n-      var fakeBorrowed = [{}, {}, {}];\n+    describe('getReadSession and getWriteSession with minReads and minWrites', function() {\n+      beforeEach(() => {\n+        sessionPool = new SessionPool(DATABASE, {\n+          minReads: 2,\n+          minWrites: 2,\n+          acquireTimeout: 50,\n+        });\n+      });\n \n-      var fakeAll = fakeReads.concat(fakeWrites, fakeBorrowed);\n+      afterEach(() => {\n+        clearTimeout(sessionPool.pingTimeoutHandle);\n+      });\n \n-      sessionPool.reads_ = fakeReads;\n-      sessionPool.writes_ = fakeWrites;\n-      sessionPool.borrowed_ = fakeBorrowed;\n+      it('should get a read session', function() {\n+        sessionPool.createTransaction_ = () => Promise.resolve();\n+        sessionPool.session_ = function() {\n+          return new Object({\n+            lastUsed: Date.now(),\n+            create: () => Promise.resolve(),\n+            delete: () => Promise.resolve(),\n+          });\n+        };\n+        sessionPool.open();\n+        return new Promise(resolve => setTimeout(resolve, 300)).then(() =>\n+          sessionPool.getReadSession().then(function(s) {\n+            assert.strictEqual(s.type, 'readonly');\n+            sessionPool.release(s);\n+          })\n+        );\n+      });\n \n-      var destroyCallCount = 0;\n-      sessionPool.destroySession_ = function(session) {\n-        assert.strictEqual(session, fakeAll[destroyCallCount++]);\n-      };\n+      it('should get a write session', function() {\n+        sessionPool.createTransaction_ = () => Promise.resolve();\n+        sessionPool.session_ = function() {\n+          return new Object({\n+            create: () => Promise.resolve(),\n+            delete: () => Promise.resolve(),\n+          });\n+        };\n+        sessionPool.open();\n+        return new Promise(resolve => setTimeout(resolve, 300)).then(() =>\n+          sessionPool.getWriteSession().then(function(s) {\n+            assert.strictEqual(s.type, 'readwrite');\n+            sessionPool.release(s);\n+          })\n+        );\n+      });\n \n-      sessionPool.close();\n+      it('should not get a read session as database is closed', function() {\n+        sessionPool.session_ = function() {\n+          return new Object({\n+            create: () => Promise.reject(),\n+            delete: () => Promise.resolve(),\n+          });\n+        };\n+        return sessionPool.getReadSession().catch(error => {\n+          assert.strictEqual(error.message, 'Database is closed.');\n+        });\n+      });\n \n-      assert.strictEqual(destroyCallCount, fakeAll.length);\n-      assert.deepEqual(sessionPool.reads_, []);\n-      assert.deepEqual(sessionPool.writes_, []);\n-      assert.deepEqual(sessionPool.borrowed_, []);\n+      it('should not get a write session as database is closed', function() {\n+        sessionPool.session_ = function() {\n+          return new Object({\n+            create: () => Promise.reject(),\n+            delete: () => Promise.resolve(),\n+          });\n+        };\n+        return sessionPool.getWriteSession().catch(error => {\n+          assert.strictEqual(error.message, 'Database is closed.');\n+        });\n+      });\n     });\n \n-    it('should settle once all sessions are destroyed', function() {\n-      var delay = 500;\n-\n-      sessionPool.reads_ = [{}];\n-      sessionPool.destroySession_ = function() {\n-        return new Promise(function(resolve) {\n-          setTimeout(resolve, delay);\n+    describe('release', function() {\n+      it('verify that a read and write session are borrowed and then release them', function() {\n+        sessionPool.createTransaction_ = () => Promise.resolve();\n+        sessionPool.session_ = function() {\n+          return new Object({\n+            create: () => Promise.resolve(),\n+            delete: () => Promise.resolve(),\n+          });\n+        };\n+        sessionPool.open();\n+        clearTimeout(sessionPool.pingTimeoutHandle);\n+        return Promise.all([\n+          sessionPool.getReadSession(),\n+          sessionPool.getWriteSession(),\n+        ]).then(function(sessions) {\n+          const readSession = sessions[0];\n+          const writeSession = sessions[1];\n+          assert.strictEqual(sessionPool.getStats().writePool.borrowed, 1);\n+          assert.strictEqual(sessionPool.getStats().readPool.borrowed, 1);\n+          return Promise.all([\n+            sessionPool.release(readSession),\n+            sessionPool.release(writeSession),\n+          ]);\n         });\n-      };\n-\n-      var end = timeSpan();\n+      });\n \n-      return sessionPool.close().then(function() {\n-        assert(isAround(delay, end()));\n+      it('should throw an error while release a write session', function() {\n+        let isSecondCall = false;\n+        sessionPool.createTransaction_ = () => {\n+          if (isSecondCall) {\n+            return Promise.reject({});\n+          }\n+          isSecondCall = true;\n+          return Promise.resolve();\n+        };\n+        sessionPool.session_ = function() {\n+          return new Object({\n+            create: () => Promise.resolve(),\n+            delete: () => Promise.resolve(),\n+          });\n+        };\n+        sessionPool.open();\n+        clearTimeout(sessionPool.pingTimeoutHandle);\n+        return sessionPool.getWriteSession().then(session =>\n+          sessionPool.release(session).then(() => {\n+            assert.strictEqual(sessionPool.getStats().writePool.available, 0);\n+          })\n+        );\n       });\n     });\n-  });\n \n-  describe('fill', function() {\n-    var created;\n+    describe('request', function() {\n+      let CONFIG;\n+      const SESSION = {\n+        formattedName_: 'formatted-name',\n+      };\n \n-    beforeEach(function() {\n-      sessionPool.options.min = 8;\n+      beforeEach(function() {\n+        CONFIG = {\n+          reqOpts: {},\n+        };\n \n-      created = {\n-        readonly: 0,\n-        readwrite: 0,\n-      };\n+        sessionPool.getReadSession = function() {\n+          return Promise.resolve(SESSION);\n+        };\n \n-      sessionPool.createSessionInBackground_ = function(type) {\n-        created[type] += 1;\n-      };\n-    });\n+        sessionPool.release = common.util.noop;\n+      });\n+\n+      it('should get a session', function(done) {\n+        sessionPool.getReadSession = function() {\n+          setImmediate(done);\n+          return Promise.resolve(SESSION);\n+        };\n \n-    it('should create the min number of required sessions', function() {\n-      sessionPool.fill();\n+        sessionPool.request(CONFIG, assert.ifError);\n+      });\n \n-      assert.strictEqual(created.readonly, 8);\n-      assert.strictEqual(created.readwrite, 0);\n-    });\n+      it('should return error if it cannot get a session', function(done) {\n+        const error = new Error('Error.');\n \n-    it('should create the min number of write sessions', function() {\n-      sessionPool.options.writes = 0.5;\n-      sessionPool.fill();\n+        sessionPool.getReadSession = function() {\n+          return Promise.reject(error);\n+        };\n \n-      assert.strictEqual(created.readonly, 4);\n-      assert.strictEqual(created.readwrite, 4);\n-    });\n+        sessionPool.request(CONFIG, function(err) {\n+          assert.strictEqual(err, error);\n+          done();\n+        });\n+      });\n \n-    it('should respect the current size of the pool', function() {\n-      sessionPool.options.writes = 0.5;\n-      sessionPool.reads_ = [{}];\n-      sessionPool.writes_ = [{}, {}];\n-      sessionPool.fill();\n+      it('should call the method with the session', function(done) {\n+        CONFIG.reqOpts = {\n+          a: 'b',\n+        };\n \n-      assert.strictEqual(created.readonly, 3);\n-      assert.strictEqual(created.readwrite, 2);\n-    });\n+        sessionPool.request_ = function(config) {\n+          assert.deepEqual(\n+            config.reqOpts,\n+            extend({}, CONFIG.reqOpts, {\n+              session: SESSION.formattedName_,\n+            })\n+          );\n+          done();\n+        };\n \n-    it('should settle once all the sessions are created', function() {\n-      var end = timeSpan();\n+        sessionPool.request(CONFIG, assert.ifError);\n+      });\n \n-      sessionPool.createSessionInBackground_ = function() {\n-        return new Promise(function(resolve) {\n-          setTimeout(resolve, 500);\n-        });\n-      };\n+      it('should release the session after calling the method', function(done) {\n+        sessionPool.release = function(session) {\n+          assert.strictEqual(session, SESSION);\n+          done();\n+        };\n+\n+        sessionPool.request_ = function(config, callback) {\n+          callback();\n+        };\n \n-      return sessionPool.fill().then(function() {\n-        assert(isAround(500, end()));\n+        sessionPool.request(CONFIG, assert.ifError);\n       });\n-    });\n-  });\n \n-  describe('getLeaks', function() {\n-    var formatTrace_;\n+      it('should execute the callback with original arguments', function(done) {\n+        const originalArgs = ['a', 'b', 'c'];\n \n-    before(function() {\n-      formatTrace_ = SessionPool.formatTrace_;\n-    });\n+        sessionPool.request_ = function(config, callback) {\n+          callback.apply(null, originalArgs);\n+        };\n \n-    after(function() {\n-      SessionPool.formatTrace_ = formatTrace_;\n+        sessionPool.request(CONFIG, function() {\n+          const args = [].slice.call(arguments);\n+          assert.deepEqual(args, originalArgs);\n+          done();\n+        });\n+      });\n     });\n \n-    it('should return a list of leaks', function() {\n-      var fakeTraces = ['abc', 'def'];\n-\n-      sessionPool.traces_ = new Map(\n-        fakeTraces.map(function(t, i) {\n-          return [i, t];\n-        })\n-      );\n+    describe('requestStream', function() {\n+      let CONFIG;\n+      let REQUEST_STREAM;\n \n-      var formatCallCount = 0;\n-      SessionPool.formatTrace_ = function(trace) {\n-        assert.strictEqual(trace, fakeTraces[formatCallCount++]);\n-        return trace\n-          .split('')\n-          .reverse()\n-          .join('');\n+      const SESSION = {\n+        formattedName_: 'formatted-name',\n       };\n \n-      var traces = sessionPool.getLeaks();\n+      beforeEach(function() {\n+        REQUEST_STREAM = through();\n \n-      assert.deepEqual(traces, ['cba', 'fed']);\n-    });\n-  });\n+        CONFIG = {\n+          reqOpts: {},\n+        };\n \n-  describe('getSession', function() {\n-    it('should call through to acquireSession_', function() {\n-      var fakeSession = {};\n+        sessionPool.requestStream_ = function() {\n+          return REQUEST_STREAM;\n+        };\n \n-      sessionPool.acquireSession_ = function(type) {\n-        assert.strictEqual(type, 'readonly');\n-        return Promise.resolve(fakeSession);\n-      };\n+        sessionPool.getReadSession = function() {\n+          return Promise.resolve(SESSION);\n+        };\n \n-      return sessionPool.getSession().then(function(session) {\n-        assert.strictEqual(session, fakeSession);\n+        sessionPool.release = common.util.noop;\n       });\n-    });\n-  });\n \n-  describe('getWriteSession', function() {\n-    it('should call through to acquireSession_', function() {\n-      var fakeSession = {};\n+      it('should get a session when stream opens', function(done) {\n+        sessionPool.getReadSession = function() {\n+          setImmediate(done);\n+          return Promise.resolve(SESSION);\n+        };\n \n-      sessionPool.acquireSession_ = function(type) {\n-        assert.strictEqual(type, 'readwrite');\n-        return Promise.resolve(fakeSession);\n-      };\n+        sessionPool.requestStream(CONFIG).emit('reading');\n+      });\n \n-      return sessionPool.getWriteSession().then(function(session) {\n-        assert.strictEqual(session, fakeSession);\n+      describe('could not get session', function() {\n+        const ERROR = new Error('Error.');\n+\n+        beforeEach(function() {\n+          sessionPool.getReadSession = function() {\n+            return Promise.reject(ERROR);\n+          };\n+        });\n+\n+        it('should destroy the stream', function(done) {\n+          sessionPool\n+            .requestStream(CONFIG)\n+            .on('error', function(err) {\n+              assert.strictEqual(err, ERROR);\n+              done();\n+            })\n+            .emit('reading');\n+        });\n       });\n-    });\n-  });\n \n-  describe('isFull', function() {\n-    it('should return true if it is full', function() {\n-      sessionPool.pendingCreates_ = 0;\n+      describe('session retrieved successfully', function() {\n+        beforeEach(function() {\n+          sessionPool.getReadSession = function() {\n+            return Promise.resolve(SESSION);\n+          };\n+        });\n \n-      sessionPool.size = function() {\n-        return sessionPool.options.max;\n-      };\n+        it('should assign session to request options', function(done) {\n+          sessionPool.requestStream_ = function(config) {\n+            assert.strictEqual(config.reqOpts.session, SESSION.formattedName_);\n+            setImmediate(done);\n+            return through.obj();\n+          };\n \n-      assert.strictEqual(sessionPool.isFull(), true);\n-    });\n+          sessionPool.requestStream(CONFIG).emit('reading');\n+        });\n \n-    it('should return true if the size + pending creates == max', function() {\n-      var pending = (sessionPool.pendingCreates_ = 21);\n+        it('should make request and pipe to the stream', function(done) {\n+          const responseData = Buffer.from('response-data');\n \n-      sessionPool.size = function() {\n-        return sessionPool.options.max - pending;\n-      };\n+          sessionPool.requestStream(CONFIG).on('data', function(data) {\n+            assert.deepEqual(data, responseData);\n+            done();\n+          });\n \n-      assert.strictEqual(sessionPool.isFull(), true);\n-    });\n+          REQUEST_STREAM.end(responseData);\n+        });\n \n-    it('should return false if not', function() {\n-      sessionPool.pendingCreates_ = 0;\n+        it('should release session when request stream ends', function(done) {\n+          sessionPool.release = function(session) {\n+            assert.strictEqual(session, SESSION);\n+            done();\n+          };\n \n-      sessionPool.size = function() {\n-        return 0;\n-      };\n+          sessionPool.requestStream(CONFIG).emit('reading');\n \n-      assert.strictEqual(sessionPool.isFull(), false);\n-    });\n-  });\n+          REQUEST_STREAM.end();\n+        });\n \n-  describe('open', function() {\n-    beforeEach(function() {\n-      sessionPool.options.min = 1;\n-      sessionPool.createSessionInBackground_ = function() {\n-        return Promise.resolve();\n-      };\n-    });\n+        it('should release session when request stream errors', function(done) {\n+          sessionPool.release = function(session) {\n+            assert.strictEqual(session, SESSION);\n+            done();\n+          };\n \n-    it('should set isOpen to true', function() {\n-      sessionPool.open();\n-      assert.strictEqual(sessionPool.isOpen, true);\n-    });\n+          sessionPool.requestStream(CONFIG).emit('reading');\n \n-    it('should create an onclose promise', function() {\n-      sessionPool.open();\n+          setImmediate(function() {\n+            REQUEST_STREAM.emit('error');\n+          });\n+        });\n+\n+        it('should error user stream when request stream errors', function(done) {\n+          const error = new Error('Error.');\n+\n+          sessionPool\n+            .requestStream(CONFIG)\n+            .on('error', function(err) {\n+              assert.strictEqual(err, error);\n+              done();\n+            })\n+            .emit('reading');\n \n-      setImmediate(function() {\n-        sessionPool.emit('close');\n+          setImmediate(function() {\n+            REQUEST_STREAM.destroy(error);\n+          });\n+        });\n       });\n \n-      return sessionPool.onClose_;\n-    });\n+      describe('abort', function() {\n+        let SESSION;\n \n-    it('should start house keeping', function(done) {\n-      sessionPool.startHouseKeeping_ = done;\n-      sessionPool.open();\n-    });\n+        beforeEach(function() {\n+          REQUEST_STREAM.cancel = common.util.noop;\n \n-    it('should emit an open event', function(done) {\n-      sessionPool.once('open', done);\n-      sessionPool.open();\n-    });\n-  });\n+          SESSION = {\n+            cancel: common.util.noop,\n+          };\n \n-  describe('release', function() {\n-    it('should throw an error for unknown sessions', function() {\n-      assert.throws(function() {\n-        sessionPool.release({});\n-      }, /Unable to release unknown session\\./);\n-    });\n+          sessionPool.getReadSession = function() {\n+            return Promise.resolve(SESSION);\n+          };\n+        });\n \n-    it('should delete the stack trace associated with the session', function() {\n-      var fakeId = 'abc';\n-      var fakeSession = {id: fakeId};\n+        it('should release the session', function(done) {\n+          sessionPool.release = function(session) {\n+            assert.strictEqual(session, SESSION);\n+            done();\n+          };\n \n-      sessionPool.borrowed_ = [fakeSession];\n-      sessionPool.traces_.set(fakeId, [{}]);\n+          const requestStream = sessionPool.requestStream(CONFIG);\n \n-      sessionPool.release(fakeSession);\n+          requestStream.emit('reading');\n \n-      var hasTrace = sessionPool.traces_.has(fakeId);\n-      assert.strictEqual(hasTrace, false);\n-    });\n+          setImmediate(function() {\n+            requestStream.abort();\n+          });\n+        });\n \n-    it('should release readonly sessions', function(done) {\n-      var fakeSession = {};\n+        it('should not release the session more than once', function(done) {\n+          let numTimesReleased = 0;\n+          sessionPool.release = function(session) {\n+            numTimesReleased++;\n+            assert.strictEqual(session, SESSION);\n+          };\n \n-      sessionPool.borrowed_ = [fakeSession];\n-      sessionPool.release_ = function(session) {\n-        assert.strictEqual(session, fakeSession);\n-        done();\n-      };\n+          const requestStream = sessionPool.requestStream(CONFIG);\n \n-      sessionPool.release(fakeSession);\n-    });\n+          requestStream.emit('reading');\n \n-    describe('readwrite sessions', function() {\n-      var fakeSession;\n+          setImmediate(function() {\n+            requestStream.abort();\n+            assert.strictEqual(numTimesReleased, 1);\n \n-      beforeEach(function() {\n-        fakeSession = {\n-          type: 'readwrite',\n-        };\n+            requestStream.abort();\n+            assert.strictEqual(numTimesReleased, 1);\n \n-        sessionPool.borrowed_ = [fakeSession];\n-      });\n+            done();\n+          });\n+        });\n \n-      it('should create a new transaction', function(done) {\n-        sessionPool.createTransaction_ = function(session) {\n-          assert.strictEqual(session, fakeSession);\n-          return Promise.resolve();\n-        };\n+        it('should cancel the request stream', function(done) {\n+          REQUEST_STREAM.cancel = done;\n \n-        sessionPool.release_ = function(session) {\n-          assert.strictEqual(session, fakeSession);\n-          done();\n-        };\n+          const requestStream = sessionPool.requestStream(CONFIG);\n \n-        sessionPool.release(fakeSession);\n+          requestStream.emit('reading');\n+\n+          setImmediate(function() {\n+            requestStream.abort();\n+          });\n+        });\n       });\n+    });\n \n-      it('should set to readonly if it fails to create a txn', function(done) {\n-        sessionPool.createTransaction_ = function() {\n-          return Promise.reject();\n+    describe('createTransaction_', function() {\n+      let fakeSession;\n+      let fakeTxn;\n+\n+      beforeEach(function() {\n+        fakeTxn = {\n+          end: function(callback) {\n+            (common.util.noop || callback)();\n+          },\n+          begin: function() {\n+            return Promise.resolve();\n+          },\n         };\n \n-        sessionPool.release_ = function(session) {\n-          assert.strictEqual(session, fakeSession);\n-          assert.strictEqual(fakeSession.type, 'readonly');\n-          done();\n+        fakeSession = {\n+          transaction: function() {\n+            return fakeTxn;\n+          },\n         };\n \n-        sessionPool.release(fakeSession);\n+        sessionPool.release = common.util.noop;\n       });\n-    });\n-  });\n-\n-  describe('request', function() {\n-    var CONFIG;\n-    var SESSION = {\n-      formattedName_: 'formatted-name',\n-    };\n-\n-    beforeEach(function() {\n-      CONFIG = {\n-        reqOpts: {},\n-      };\n-\n-      sessionPool.getSession = function() {\n-        return Promise.resolve(SESSION);\n-      };\n \n-      sessionPool.release = common.util.noop;\n-    });\n+      it('should create a txn object', function(done) {\n+        const fakeOptions = {};\n \n-    it('should get a session', function(done) {\n-      sessionPool.getSession = function() {\n-        setImmediate(done);\n-        return Promise.resolve(SESSION);\n-      };\n+        fakeSession.transaction = function(options) {\n+          assert.strictEqual(options, fakeOptions);\n+          setImmediate(done);\n+          return fakeTxn;\n+        };\n \n-      sessionPool.request(CONFIG, assert.ifError);\n-    });\n+        sessionPool.createTransaction_(fakeSession, fakeOptions);","path":"test/session-pool.js","position":1258,"original_position":1258,"commit_id":"d64318bb442125bad1a3533708c889fe587f27f3","original_commit_id":"3b9ab7d8746ae92c579e9237b0d10554f1490379","user":{"login":"anup-honey","id":28715793,"node_id":"MDQ6VXNlcjI4NzE1Nzkz","avatar_url":"https://avatars2.githubusercontent.com/u/28715793?v=4","gravatar_id":"","url":"https://api.github.com/users/anup-honey","html_url":"https://github.com/anup-honey","followers_url":"https://api.github.com/users/anup-honey/followers","following_url":"https://api.github.com/users/anup-honey/following{/other_user}","gists_url":"https://api.github.com/users/anup-honey/gists{/gist_id}","starred_url":"https://api.github.com/users/anup-honey/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/anup-honey/subscriptions","organizations_url":"https://api.github.com/users/anup-honey/orgs","repos_url":"https://api.github.com/users/anup-honey/repos","events_url":"https://api.github.com/users/anup-honey/events{/privacy}","received_events_url":"https://api.github.com/users/anup-honey/received_events","type":"User","site_admin":false},"body":"Fixed it","created_at":"2018-06-19T20:45:43Z","updated_at":"2018-06-19T20:45:43Z","html_url":"https://github.com/googleapis/nodejs-spanner/pull/194#discussion_r196572217","pull_request_url":"https://api.github.com/repos/googleapis/nodejs-spanner/pulls/194","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/googleapis/nodejs-spanner/pulls/comments/196572217"},"html":{"href":"https://github.com/googleapis/nodejs-spanner/pull/194#discussion_r196572217"},"pull_request":{"href":"https://api.github.com/repos/googleapis/nodejs-spanner/pulls/194"}},"in_reply_to_id":195592323},"pull_request":{"url":"https://api.github.com/repos/googleapis/nodejs-spanner/pulls/194","id":187571764,"node_id":"MDExOlB1bGxSZXF1ZXN0MTg3NTcxNzY0","html_url":"https://github.com/googleapis/nodejs-spanner/pull/194","diff_url":"https://github.com/googleapis/nodejs-spanner/pull/194.diff","patch_url":"https://github.com/googleapis/nodejs-spanner/pull/194.patch","issue_url":"https://api.github.com/repos/googleapis/nodejs-spanner/issues/194","number":194,"state":"open","locked":false,"title":"AB - New session pool code","user":{"login":"anup-honey","id":28715793,"node_id":"MDQ6VXNlcjI4NzE1Nzkz","avatar_url":"https://avatars2.githubusercontent.com/u/28715793?v=4","gravatar_id":"","url":"https://api.github.com/users/anup-honey","html_url":"https://github.com/anup-honey","followers_url":"https://api.github.com/users/anup-honey/followers","following_url":"https://api.github.com/users/anup-honey/following{/other_user}","gists_url":"https://api.github.com/users/anup-honey/gists{/gist_id}","starred_url":"https://api.github.com/users/anup-honey/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/anup-honey/subscriptions","organizations_url":"https://api.github.com/users/anup-honey/orgs","repos_url":"https://api.github.com/users/anup-honey/repos","events_url":"https://api.github.com/users/anup-honey/events{/privacy}","received_events_url":"https://api.github.com/users/anup-honey/received_events","type":"User","site_admin":false},"body":"Fixes #192\r\n\r\n- [x] Tests and linter pass\r\n- [x] Code coverage does not decrease (if any source code was changed)\r\n- [ ] Appropriate docs were updated (if necessary)\r\n     --- It is a major change. I have updated the documentation inside the code.\r\n","created_at":"2018-05-11T21:15:51Z","updated_at":"2018-06-19T20:45:43Z","closed_at":null,"merged_at":null,"merge_commit_sha":"ecedbfb5be3e21817404a12567e6cf92818203a6","assignee":{"login":"callmehiphop","id":1707267,"node_id":"MDQ6VXNlcjE3MDcyNjc=","avatar_url":"https://avatars3.githubusercontent.com/u/1707267?v=4","gravatar_id":"","url":"https://api.github.com/users/callmehiphop","html_url":"https://github.com/callmehiphop","followers_url":"https://api.github.com/users/callmehiphop/followers","following_url":"https://api.github.com/users/callmehiphop/following{/other_user}","gists_url":"https://api.github.com/users/callmehiphop/gists{/gist_id}","starred_url":"https://api.github.com/users/callmehiphop/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/callmehiphop/subscriptions","organizations_url":"https://api.github.com/users/callmehiphop/orgs","repos_url":"https://api.github.com/users/callmehiphop/repos","events_url":"https://api.github.com/users/callmehiphop/events{/privacy}","received_events_url":"https://api.github.com/users/callmehiphop/received_events","type":"User","site_admin":false},"assignees":[{"login":"callmehiphop","id":1707267,"node_id":"MDQ6VXNlcjE3MDcyNjc=","avatar_url":"https://avatars3.githubusercontent.com/u/1707267?v=4","gravatar_id":"","url":"https://api.github.com/users/callmehiphop","html_url":"https://github.com/callmehiphop","followers_url":"https://api.github.com/users/callmehiphop/followers","following_url":"https://api.github.com/users/callmehiphop/following{/other_user}","gists_url":"https://api.github.com/users/callmehiphop/gists{/gist_id}","starred_url":"https://api.github.com/users/callmehiphop/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/callmehiphop/subscriptions","organizations_url":"https://api.github.com/users/callmehiphop/orgs","repos_url":"https://api.github.com/users/callmehiphop/repos","events_url":"https://api.github.com/users/callmehiphop/events{/privacy}","received_events_url":"https://api.github.com/users/callmehiphop/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":725910257,"node_id":"MDU6TGFiZWw3MjU5MTAyNTc=","url":"https://api.github.com/repos/googleapis/nodejs-spanner/labels/cla:%20no","name":"cla: no","color":"d93f0b","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/googleapis/nodejs-spanner/pulls/194/commits","review_comments_url":"https://api.github.com/repos/googleapis/nodejs-spanner/pulls/194/comments","review_comment_url":"https://api.github.com/repos/googleapis/nodejs-spanner/pulls/comments{/number}","comments_url":"https://api.github.com/repos/googleapis/nodejs-spanner/issues/194/comments","statuses_url":"https://api.github.com/repos/googleapis/nodejs-spanner/statuses/d64318bb442125bad1a3533708c889fe587f27f3","head":{"label":"honeyscience:master","ref":"master","sha":"d64318bb442125bad1a3533708c889fe587f27f3","user":{"login":"honeyscience","id":8597318,"node_id":"MDEyOk9yZ2FuaXphdGlvbjg1OTczMTg=","avatar_url":"https://avatars1.githubusercontent.com/u/8597318?v=4","gravatar_id":"","url":"https://api.github.com/users/honeyscience","html_url":"https://github.com/honeyscience","followers_url":"https://api.github.com/users/honeyscience/followers","following_url":"https://api.github.com/users/honeyscience/following{/other_user}","gists_url":"https://api.github.com/users/honeyscience/gists{/gist_id}","starred_url":"https://api.github.com/users/honeyscience/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/honeyscience/subscriptions","organizations_url":"https://api.github.com/users/honeyscience/orgs","repos_url":"https://api.github.com/users/honeyscience/repos","events_url":"https://api.github.com/users/honeyscience/events{/privacy}","received_events_url":"https://api.github.com/users/honeyscience/received_events","type":"Organization","site_admin":false},"repo":{"id":123182299,"node_id":"MDEwOlJlcG9zaXRvcnkxMjMxODIyOTk=","name":"nodejs-spanner","full_name":"honeyscience/nodejs-spanner","owner":{"login":"honeyscience","id":8597318,"node_id":"MDEyOk9yZ2FuaXphdGlvbjg1OTczMTg=","avatar_url":"https://avatars1.githubusercontent.com/u/8597318?v=4","gravatar_id":"","url":"https://api.github.com/users/honeyscience","html_url":"https://github.com/honeyscience","followers_url":"https://api.github.com/users/honeyscience/followers","following_url":"https://api.github.com/users/honeyscience/following{/other_user}","gists_url":"https://api.github.com/users/honeyscience/gists{/gist_id}","starred_url":"https://api.github.com/users/honeyscience/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/honeyscience/subscriptions","organizations_url":"https://api.github.com/users/honeyscience/orgs","repos_url":"https://api.github.com/users/honeyscience/repos","events_url":"https://api.github.com/users/honeyscience/events{/privacy}","received_events_url":"https://api.github.com/users/honeyscience/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/honeyscience/nodejs-spanner","description":"Node.js client for Google Cloud Spanner: the world’s first fully managed relational database service to offer both strong consistency and horizontal scalability.","fork":true,"url":"https://api.github.com/repos/honeyscience/nodejs-spanner","forks_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/forks","keys_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/keys{/key_id}","collaborators_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/teams","hooks_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/hooks","issue_events_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/issues/events{/number}","events_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/events","assignees_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/assignees{/user}","branches_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/branches{/branch}","tags_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/tags","blobs_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/git/refs{/sha}","trees_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/git/trees{/sha}","statuses_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/statuses/{sha}","languages_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/languages","stargazers_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/stargazers","contributors_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/contributors","subscribers_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/subscribers","subscription_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/subscription","commits_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/commits{/sha}","git_commits_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/git/commits{/sha}","comments_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/comments{/number}","issue_comment_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/issues/comments{/number}","contents_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/contents/{+path}","compare_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/compare/{base}...{head}","merges_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/merges","archive_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/downloads","issues_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/issues{/number}","pulls_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/pulls{/number}","milestones_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/milestones{/number}","notifications_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/labels{/name}","releases_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/releases{/id}","deployments_url":"https://api.github.com/repos/honeyscience/nodejs-spanner/deployments","created_at":"2018-02-27T20:11:26Z","updated_at":"2018-06-18T20:45:40Z","pushed_at":"2018-06-18T20:45:38Z","git_url":"git://github.com/honeyscience/nodejs-spanner.git","ssh_url":"git@github.com:honeyscience/nodejs-spanner.git","clone_url":"https://github.com/honeyscience/nodejs-spanner.git","svn_url":"https://github.com/honeyscience/nodejs-spanner","homepage":"https://cloud.google.com/spanner/","size":2082,"stargazers_count":1,"watchers_count":1,"language":"JavaScript","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":0,"watchers":1,"default_branch":"master"}},"base":{"label":"googleapis:master","ref":"master","sha":"a7e19825dffeaedcd4be03c1332a9749e8d56d19","user":{"login":"googleapis","id":16785467,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2Nzg1NDY3","avatar_url":"https://avatars3.githubusercontent.com/u/16785467?v=4","gravatar_id":"","url":"https://api.github.com/users/googleapis","html_url":"https://github.com/googleapis","followers_url":"https://api.github.com/users/googleapis/followers","following_url":"https://api.github.com/users/googleapis/following{/other_user}","gists_url":"https://api.github.com/users/googleapis/gists{/gist_id}","starred_url":"https://api.github.com/users/googleapis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/googleapis/subscriptions","organizations_url":"https://api.github.com/users/googleapis/orgs","repos_url":"https://api.github.com/users/googleapis/repos","events_url":"https://api.github.com/users/googleapis/events{/privacy}","received_events_url":"https://api.github.com/users/googleapis/received_events","type":"Organization","site_admin":false},"repo":{"id":107570416,"node_id":"MDEwOlJlcG9zaXRvcnkxMDc1NzA0MTY=","name":"nodejs-spanner","full_name":"googleapis/nodejs-spanner","owner":{"login":"googleapis","id":16785467,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2Nzg1NDY3","avatar_url":"https://avatars3.githubusercontent.com/u/16785467?v=4","gravatar_id":"","url":"https://api.github.com/users/googleapis","html_url":"https://github.com/googleapis","followers_url":"https://api.github.com/users/googleapis/followers","following_url":"https://api.github.com/users/googleapis/following{/other_user}","gists_url":"https://api.github.com/users/googleapis/gists{/gist_id}","starred_url":"https://api.github.com/users/googleapis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/googleapis/subscriptions","organizations_url":"https://api.github.com/users/googleapis/orgs","repos_url":"https://api.github.com/users/googleapis/repos","events_url":"https://api.github.com/users/googleapis/events{/privacy}","received_events_url":"https://api.github.com/users/googleapis/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/googleapis/nodejs-spanner","description":"Node.js client for Google Cloud Spanner: the world’s first fully managed relational database service to offer both strong consistency and horizontal scalability.","fork":false,"url":"https://api.github.com/repos/googleapis/nodejs-spanner","forks_url":"https://api.github.com/repos/googleapis/nodejs-spanner/forks","keys_url":"https://api.github.com/repos/googleapis/nodejs-spanner/keys{/key_id}","collaborators_url":"https://api.github.com/repos/googleapis/nodejs-spanner/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/googleapis/nodejs-spanner/teams","hooks_url":"https://api.github.com/repos/googleapis/nodejs-spanner/hooks","issue_events_url":"https://api.github.com/repos/googleapis/nodejs-spanner/issues/events{/number}","events_url":"https://api.github.com/repos/googleapis/nodejs-spanner/events","assignees_url":"https://api.github.com/repos/googleapis/nodejs-spanner/assignees{/user}","branches_url":"https://api.github.com/repos/googleapis/nodejs-spanner/branches{/branch}","tags_url":"https://api.github.com/repos/googleapis/nodejs-spanner/tags","blobs_url":"https://api.github.com/repos/googleapis/nodejs-spanner/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/googleapis/nodejs-spanner/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/googleapis/nodejs-spanner/git/refs{/sha}","trees_url":"https://api.github.com/repos/googleapis/nodejs-spanner/git/trees{/sha}","statuses_url":"https://api.github.com/repos/googleapis/nodejs-spanner/statuses/{sha}","languages_url":"https://api.github.com/repos/googleapis/nodejs-spanner/languages","stargazers_url":"https://api.github.com/repos/googleapis/nodejs-spanner/stargazers","contributors_url":"https://api.github.com/repos/googleapis/nodejs-spanner/contributors","subscribers_url":"https://api.github.com/repos/googleapis/nodejs-spanner/subscribers","subscription_url":"https://api.github.com/repos/googleapis/nodejs-spanner/subscription","commits_url":"https://api.github.com/repos/googleapis/nodejs-spanner/commits{/sha}","git_commits_url":"https://api.github.com/repos/googleapis/nodejs-spanner/git/commits{/sha}","comments_url":"https://api.github.com/repos/googleapis/nodejs-spanner/comments{/number}","issue_comment_url":"https://api.github.com/repos/googleapis/nodejs-spanner/issues/comments{/number}","contents_url":"https://api.github.com/repos/googleapis/nodejs-spanner/contents/{+path}","compare_url":"https://api.github.com/repos/googleapis/nodejs-spanner/compare/{base}...{head}","merges_url":"https://api.github.com/repos/googleapis/nodejs-spanner/merges","archive_url":"https://api.github.com/repos/googleapis/nodejs-spanner/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/googleapis/nodejs-spanner/downloads","issues_url":"https://api.github.com/repos/googleapis/nodejs-spanner/issues{/number}","pulls_url":"https://api.github.com/repos/googleapis/nodejs-spanner/pulls{/number}","milestones_url":"https://api.github.com/repos/googleapis/nodejs-spanner/milestones{/number}","notifications_url":"https://api.github.com/repos/googleapis/nodejs-spanner/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/googleapis/nodejs-spanner/labels{/name}","releases_url":"https://api.github.com/repos/googleapis/nodejs-spanner/releases{/id}","deployments_url":"https://api.github.com/repos/googleapis/nodejs-spanner/deployments","created_at":"2017-10-19T16:17:31Z","updated_at":"2018-06-19T17:37:39Z","pushed_at":"2018-06-19T17:37:43Z","git_url":"git://github.com/googleapis/nodejs-spanner.git","ssh_url":"git@github.com:googleapis/nodejs-spanner.git","clone_url":"https://github.com/googleapis/nodejs-spanner.git","svn_url":"https://github.com/googleapis/nodejs-spanner","homepage":"https://cloud.google.com/spanner/","size":1843,"stargazers_count":25,"watchers_count":25,"language":"JavaScript","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":24,"mirror_url":null,"archived":false,"open_issues_count":19,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":24,"open_issues":19,"watchers":25,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/googleapis/nodejs-spanner/pulls/194"},"html":{"href":"https://github.com/googleapis/nodejs-spanner/pull/194"},"issue":{"href":"https://api.github.com/repos/googleapis/nodejs-spanner/issues/194"},"comments":{"href":"https://api.github.com/repos/googleapis/nodejs-spanner/issues/194/comments"},"review_comments":{"href":"https://api.github.com/repos/googleapis/nodejs-spanner/pulls/194/comments"},"review_comment":{"href":"https://api.github.com/repos/googleapis/nodejs-spanner/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/googleapis/nodejs-spanner/pulls/194/commits"},"statuses":{"href":"https://api.github.com/repos/googleapis/nodejs-spanner/statuses/d64318bb442125bad1a3533708c889fe587f27f3"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"anup-honey"}}