├── .gitignore ├── _config.yml ├── .travis.yml ├── .jshintrc ├── lib ├── preparedStatement.js ├── join.js ├── parseConnection.js ├── quoteString.js ├── sqlObject.js ├── pooledConnection.js ├── prepare.js ├── statement.js ├── connectionObject.js ├── dynamicPool.js ├── staticPool.js └── connection.js ├── index.js ├── .vscode └── launch.json ├── .editorconfig ├── LICENSE ├── .npmignore ├── package.json ├── README.md └── index.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "13" 5 | - "8" 6 | 7 | cache: npm 8 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 6, 3 | "undef": true, 4 | "unused": true, 5 | "globals": { 6 | } 7 | } -------------------------------------------------------------------------------- /lib/preparedStatement.js: -------------------------------------------------------------------------------- 1 | var Statement = require('./statement'); 2 | 3 | class PreparedStatement extends Statement { 4 | constructor(connection, sql) { 5 | super(connection, sql); 6 | this.prepare(); 7 | } 8 | } 9 | 10 | module.exports = PreparedStatement; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Connection = require('./lib/connection'); 2 | var StaticPool = require('./lib/staticPool'); 3 | var DynamicPool = require('./lib/dynamicPool'); 4 | var ParseConnection = require('./lib/parseConnection'); 5 | 6 | module.exports = { 7 | Connection: Connection, 8 | StaticPool: StaticPool, 9 | DynamicPool: DynamicPool, 10 | ParseConnection: ParseConnection 11 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "type": "node", 10 | "request": "launch", 11 | "name": "Launch Program", 12 | "program": "${workspaceRoot}/test.js" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This project uses EditorConfig (http://editorconfig.org) to standardize 2 | # tabulation, charset and other attributes of source files. Please head to 3 | # http://editorconfig.org/#download to check if your editor comes with 4 | # EditorConfig bundled or to download a plugin. 5 | 6 | root = true 7 | 8 | [*] 9 | charset = utf-8 10 | indent_style = tab 11 | indent_size = 4 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | 15 | [{package.json,*.yml}] 16 | indent_style = space 17 | indent_size = 2 -------------------------------------------------------------------------------- /lib/join.js: -------------------------------------------------------------------------------- 1 | var QuoteString = require('./quoteString'); 2 | 3 | function Join(preparedSql, args) { 4 | if (preparedSql.indexes.length !== args.length) { 5 | throw new Error("Number of parameters does not match required parameters"); 6 | } 7 | 8 | let sql = preparedSql.sql; 9 | let offset = 0; 10 | args.map((o, n) => { 11 | o = QuoteString(o); 12 | sql = sql.substr(0, preparedSql.indexes[n] + offset) + o + sql.substr(preparedSql.indexes[n] + offset + 1); 13 | offset += o.length - 1; 14 | }); 15 | 16 | return sql; 17 | } 18 | 19 | module.exports = Join; -------------------------------------------------------------------------------- /lib/parseConnection.js: -------------------------------------------------------------------------------- 1 | var ConnectionObject = require('./connectionObject'); 2 | 3 | var m_connectionRegex = /^([^:]+):\/\/(?:([^:]+)(?::([^@\/]+))?@)?(?:([^\/:]+)(?::(\d+))?)?\/([^\?]+)(?:\?(.*))?$/; 4 | 5 | /** 6 | * Parses a connection string to extract the driver name, 7 | * username, password, hostname, port, database, and 8 | * parameter string. 9 | * 10 | * @param {string} connectionString 11 | * @param {any} driver 12 | * @returns {ConnectionObject} 13 | */ 14 | function ParseConnection(connectionString, driver) { 15 | [match, driverName, username, password, hostname, port, database, parameters] = m_connectionRegex.exec(connectionString); 16 | return new ConnectionObject(driverName, username, password, hostname, port, database, parameters, driver); 17 | } 18 | 19 | module.exports = ParseConnection; -------------------------------------------------------------------------------- /lib/quoteString.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Quotes a string for SQL statements 3 | * 4 | * @param {string|number} string 5 | * @returns {string} the quoted string, or the passed parameter if it is a finite number 6 | */ 7 | function QuoteString(string) { 8 | if (typeof string === 'number') { 9 | if (Number.isFinite(string) === false) { 10 | return QuoteString(string.toString()); 11 | } 12 | return string.toString(); 13 | } else if (string === null) { 14 | return 'null'; 15 | } 16 | string = string.toString(); 17 | string = string.replace(/\\/g, '\\\\'); 18 | string = string.replace(/\x00/g, '\\x00'); 19 | string = string.replace(/\n/g, '\\n'); 20 | string = string.replace(/\r/g, '\\r'); 21 | string = string.replace(/'/g, "''"); 22 | return "'" + string + "'"; 23 | } 24 | 25 | module.exports = QuoteString; 26 | -------------------------------------------------------------------------------- /lib/sqlObject.js: -------------------------------------------------------------------------------- 1 | var QuoteString = require('./quoteString'); 2 | 3 | class SqlObject { 4 | constructor(chunks, indexes) { 5 | this.sql = chunks.join("'"); 6 | this.indexes = indexes; 7 | } 8 | 9 | /** 10 | * 11 | * 12 | * @param {array} args 13 | * @returns {string} Prepared SQL statement 14 | * @memberof SqlObject 15 | */ 16 | map(args) { 17 | if (this.indexes.length !== args.length) { 18 | throw new Error("Number of parameters does not match required parameters"); 19 | } 20 | 21 | let sql = this.sql; 22 | let offset = 0; 23 | args.map((o, n) => { 24 | o = QuoteString(o); 25 | sql = sql.substr(0, this.indexes[n] + offset) + o + sql.substr(this.indexes[n] + offset + 1); 26 | offset += o.length - 1; 27 | }); 28 | 29 | return sql; 30 | } 31 | } 32 | 33 | module.exports = SqlObject; -------------------------------------------------------------------------------- /lib/pooledConnection.js: -------------------------------------------------------------------------------- 1 | var Connection = require('./connection'); 2 | var StaticPool = require('./staticPool'); 3 | 4 | class PooledConnection extends Connection { 5 | /** 6 | * Creates an instance of PooledConnection. 7 | * @param {StaticPool} pool 8 | * @param {string} connectionString 9 | * @param {any} driver 10 | * @memberof PooledConnection 11 | */ 12 | constructor(pool, connectionString, driver) { 13 | super(connectionString, driver); 14 | 15 | if (typeof this.Driver.pool === "function") { 16 | this.Driver.pool(this); 17 | } 18 | 19 | this.__pool = pool; 20 | } 21 | 22 | kill() { 23 | return super.close(); 24 | } 25 | 26 | /** 27 | * Frees this connection for the pool 28 | * 29 | * @returns {Promise} 30 | * @memberof PooledConnection 31 | */ 32 | close() { 33 | return new Promise((resolve, reject) => { 34 | this.__pool.__closeConnection(this); 35 | resolve(true); 36 | }); 37 | } 38 | } 39 | 40 | module.exports = PooledConnection; -------------------------------------------------------------------------------- /lib/prepare.js: -------------------------------------------------------------------------------- 1 | var SqlObject = require('./sqlObject'); 2 | 3 | /** 4 | * Prepares an SQL statement for use. Parses it into chunks of quoted 5 | * strings and non-quoted strings. Maps all the ?'s for future use. 6 | * 7 | * @param {string} sql 8 | * @returns {SqlObject} 9 | */ 10 | function Prepare(sql) { 11 | // break the sql into chunks of quoted and none quoted 12 | // every odd indexed element is quoted 13 | let chunks = sql.split("'"); 14 | let indexes = []; 15 | let pos = 0; 16 | 17 | if (chunks.length % 2 === 0) { 18 | // if there's an even number of chunks, then the quotes aren't balanced 19 | throw new Error("Unbalanced quotes"); 20 | } 21 | 22 | chunks.map((chunk, n) => { 23 | if (n % 2 === 1) { 24 | pos += chunk.length + 2; 25 | return; 26 | } 27 | let subPos = -1; 28 | while ((subPos = chunk.indexOf("?", subPos + 1)) >= 0) { 29 | indexes.push(pos + subPos); 30 | } 31 | pos += chunk.length; 32 | }); 33 | 34 | return new SqlObject(chunks, indexes); 35 | } 36 | 37 | module.exports = Prepare; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 mlaanderson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # Example directories 61 | examples/ 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "database-js", 3 | "version": "3.0.11", 4 | "description": "Common database interface for JavaScript", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Automated tests will be added soon.\"" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/mlaanderson/database-js.git" 12 | }, 13 | "keywords": [ 14 | "database-js", 15 | "database-js-mysql", 16 | "database-js-postgres", 17 | "database-js-sqlite", 18 | "database-js-adodb", 19 | "database-js-firebase", 20 | "database-js-ini", 21 | "database-js-xlsx", 22 | "database-js-csv", 23 | "database-js-json", 24 | "javascript", 25 | "database", 26 | "db", 27 | "sql", 28 | "query", 29 | "driver", 30 | "mysql", 31 | "postgre", 32 | "postgres", 33 | "postgresql", 34 | "sqlite", 35 | "ado", 36 | "adodb", 37 | "firebase", 38 | "ini", 39 | "xls", 40 | "xlsx", 41 | "csv", 42 | "json" 43 | ], 44 | "author": "Michael Anderson ", 45 | "contributors": [ 46 | "Michael Anderson (https://github.com/mlaanderson/)", 47 | "Thiago Delgado Pinto (https://github.com/thiagodp)" 48 | ], 49 | "license": "MIT", 50 | "bugs": { 51 | "url": "https://github.com/mlaanderson/database-js/issues" 52 | }, 53 | "homepage": "https://github.com/mlaanderson/database-js" 54 | } 55 | -------------------------------------------------------------------------------- /lib/statement.js: -------------------------------------------------------------------------------- 1 | var SqlObject = require('./sqlObject'); 2 | var Prepare = require('./prepare'); 3 | 4 | var m_parent = Symbol('parent'); 5 | var m_sql = Symbol('sql'); 6 | var m_preparedSql = Symbol('preparedSql'); 7 | 8 | class Statement { 9 | constructor(connection, sql) { 10 | this[m_parent] = connection; 11 | this[m_sql] = sql; 12 | if (! sql) { 13 | throw new Error("sql is not set"); 14 | } 15 | } 16 | 17 | /** 18 | * @private 19 | * @readonly 20 | * @returns {Connection} 21 | * @memberof Statement 22 | */ 23 | get parent() { 24 | return this[m_parent]; 25 | } 26 | 27 | /** 28 | * @private 29 | * @readonly 30 | * @returns {SqlObject} 31 | * @memberof Statement 32 | */ 33 | get preparedSql() { 34 | return this[m_preparedSql]; 35 | } 36 | 37 | /** 38 | * @private 39 | * @readonly 40 | * @returns {string} 41 | * @memberof Statement 42 | */ 43 | get sql() { 44 | return this[m_sql]; 45 | } 46 | 47 | /** 48 | * Executes the SQL query with the given arguments. 49 | * 50 | * @param {any} args 51 | * @returns {Promise} 52 | * @memberof Statement 53 | */ 54 | query(... args) { 55 | return new Promise((resolve, reject) => { 56 | let sqlString; 57 | if (this.preparedSql) { 58 | sqlString = this.preparedSql.map(args); 59 | } else { 60 | sqlString = this.sql; 61 | } 62 | this.parent.__connection.query(sqlString) 63 | .then(response => resolve(response)) 64 | .catch(reason => reject(reason)); 65 | }); 66 | } 67 | 68 | /** 69 | * Prepares the statement for use with arguments. 70 | * 71 | * @memberof Statement 72 | */ 73 | prepare() { 74 | this[m_preparedSql] = Prepare(this.sql); 75 | } 76 | 77 | /** 78 | * Executes the SQL statement with the given arguments. 79 | * 80 | * @param {any} args 81 | * @returns {Promise} 82 | * @memberof Statement 83 | */ 84 | execute(... args) { 85 | return new Promise((resolve, reject) => { 86 | let sqlString; 87 | if (this.preparedSql) { 88 | sqlString = this.preparedSql.map(args); 89 | } else { 90 | sqlString = this.sql; 91 | } 92 | this.parent.__connection.execute(sqlString) 93 | .then(result => resolve(result)) 94 | .catch(reason => reject(reason)); 95 | }); 96 | } 97 | } 98 | 99 | module.exports = Statement; -------------------------------------------------------------------------------- /lib/connectionObject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Loads a module or returns false if it is not available. 3 | * @param {string} driver_name 4 | */ 5 | function loadModule(driver_name) { 6 | try { 7 | require.resolve(driver_name); 8 | return require(driver_name); 9 | } catch (error) { 10 | return false; 11 | } 12 | } 13 | 14 | /** 15 | * Maps the connection components into a constrained object. Adds the 16 | * parseParameters helper function 17 | * 18 | * @class ConnectionObject 19 | */ 20 | class ConnectionObject { 21 | 22 | /** 23 | * Creates an instance of ConnectionObject. 24 | * @param {string} driverName 25 | * @param {string} username 26 | * @param {string} password 27 | * @param {string} hostname 28 | * @param {string} port 29 | * @param {string} database 30 | * @param {string} parameters 31 | * @param {any} driver 32 | * @memberof ConnectionObject 33 | */ 34 | constructor(driverName, username, password, hostname, port, database, parameters, driver) { 35 | this.DriverName = driverName; 36 | this.Driver = driver || loadModule('database-js-' + driverName) || loadModule('jsdbc-' + driverName) || require(driverName); 37 | this.Username = username; 38 | this.Password = password; 39 | this.Hostname = hostname; 40 | this.Port = port; 41 | this.Database = database; 42 | this.Parameters = parameters; 43 | } 44 | 45 | /** 46 | * Splits the parameter string into an object of key/value pairs 47 | * 48 | * @returns {any} 49 | * @memberof ConnectionObject 50 | */ 51 | parseParameters() { 52 | var results = {}; 53 | var params = this.Parameters.split(/&/g); 54 | params.map((p) => { 55 | let parts = p.split('='); 56 | results[parts[0]] = parts[1]; 57 | }); 58 | 59 | return results; 60 | } 61 | 62 | /** 63 | * Makes a URL from the parameters. 64 | * 65 | * @return {string} 66 | * @memberof ConnectionObject 67 | */ 68 | makeURL() { 69 | return this.DriverName + '://' + 70 | ( this.Username ? this.Username : '' ) + 71 | ( this.Password ? ':' + this.Password : '' ) + 72 | ( this.Hostname ? '@' + this.Hostname : '' ) + 73 | ( this.Port ? ':' + this.Port : '' ) + 74 | '/' + this.Database + 75 | ( this.Parameters ? '?' + this.parseParameters() : '' ) 76 | ; 77 | } 78 | 79 | /** 80 | * Creates a new connection object from a plain object. 81 | * 82 | * @param {object} obj 83 | * @param {any} driver 84 | * @returns {ConnectionObject} 85 | * @memberof ConnectionObject 86 | */ 87 | static fromPlain( obj, driver ) { 88 | return new ConnectionObject( 89 | obj[ 'driverName' ] || obj[ 'DriverName' ], 90 | obj[ 'username' ] || obj[ 'Username' ], 91 | obj[ 'password' ] || obj[ 'Password' ], 92 | obj[ 'hostname' ] || obj[ 'Hostname' ], 93 | obj[ 'port' ] || obj[ 'Port' ], 94 | obj[ 'database' ] || obj[ 'Database' ], 95 | obj[ 'parameters' ] || obj[ 'Parameters' ], 96 | driver 97 | ); 98 | } 99 | 100 | } 101 | 102 | module.exports = ConnectionObject; -------------------------------------------------------------------------------- /lib/dynamicPool.js: -------------------------------------------------------------------------------- 1 | var PooledConnection = require('./pooledConnection'); 2 | 3 | class DynamicPool { 4 | 5 | /** 6 | * Constructs a dynamic pool. 7 | * 8 | * @param {string|ConnectionObject} conn Connection string or object. 9 | * @param {int} poolSize Pool size (optional). 10 | * @param {any} driver Driver to use (optional). 11 | * @memberof DynamicPool 12 | */ 13 | constructor(conn, poolSize = 10, driver = undefined) { 14 | 15 | let url = 'string' === typeof conn ? conn : conn.makeURL(); 16 | 17 | /** @type {string} */ 18 | this.__url = url; 19 | /** @type {number} */ 20 | this.__poolSize = poolSize; 21 | /** @type {class} */ 22 | this.__driver = driver; 23 | /** @type {Array} */ 24 | this.__available = []; 25 | /** @type {Array} */ 26 | this.__inUse = []; 27 | 28 | while (this.Size > this.Count) { 29 | this.__addConnection(); 30 | } 31 | } 32 | 33 | /** 34 | * @private 35 | * @memberof StaticPool 36 | */ 37 | __addConnection() { 38 | let connection = new PooledConnection(this, this.__url, this.__driver); 39 | this.__available.push(connection); 40 | } 41 | 42 | /** 43 | * @private 44 | * @param {PooledConnection} connection 45 | * @memberof StaticPool 46 | */ 47 | __closeConnection(connection) { 48 | let index = this.__inUse.indexOf(connection); 49 | if (index >= 0) { 50 | this.__inUse.splice(index, 1); 51 | this.__available.push(connection); 52 | } 53 | } 54 | 55 | /** 56 | * The number of available connections in the pool 57 | * @returns {number} 58 | * @readonly 59 | * @memberof StaticPool 60 | */ 61 | get Available() { 62 | return this.__available.length; 63 | } 64 | 65 | /** 66 | * The number of used connections in the pool 67 | * @returns {number} 68 | * @readonly 69 | * @memberof StaticPool 70 | */ 71 | get InUse() { 72 | return this.__inUse.length; 73 | } 74 | 75 | /** 76 | * The total number of connections in the pool 77 | * @returns {number} 78 | * @readonly 79 | * @memberof StaticPool 80 | */ 81 | get Count() { 82 | return this.Available + this.InUse; 83 | } 84 | 85 | /** 86 | * The prefered number of connections in the pool 87 | * @returns {number} 88 | * @readonly 89 | * @memberof StaticPool 90 | */ 91 | get Size() { 92 | return this.__poolSize; 93 | } 94 | 95 | /** 96 | * Grabs an available connection from the pool 97 | * 98 | * @returns {PooledConnection} 99 | * @memberof StaticPool 100 | */ 101 | getConnection() { 102 | if (this.__available.length <= 0) { 103 | this.__addConnection(); 104 | } 105 | let connection = this.__available.pop(); 106 | this.__inUse.push(connection); 107 | return connection; 108 | } 109 | 110 | /** 111 | * Closes the underlying connections and empties the pool 112 | * 113 | * @returns {Promise>} 114 | * @memberof StaticPool 115 | */ 116 | close() { 117 | /** @type {Array>} */ 118 | let promises = this.__available.concat(this.__inUse).map(el => el.kill()); 119 | return Promise.all(promises).then(values => { 120 | while (this.InUse > 0) { this.__inUse.pop(); } 121 | while (this.Available > 0) { this.__available.pop(); } 122 | }); 123 | } 124 | } 125 | 126 | module.exports = DynamicPool; -------------------------------------------------------------------------------- /lib/staticPool.js: -------------------------------------------------------------------------------- 1 | var PooledConnection = require('./pooledConnection'); 2 | 3 | var AvailablePool = {}; 4 | var InUsePool = {}; 5 | 6 | class StaticPool { 7 | 8 | /** 9 | * Constructs a static pool. 10 | * 11 | * @param {string|ConnectionObject} conn Connection string or object. 12 | * @param {int} poolSize Pool size (optional). 13 | * @param {any} driver Driver to use (optional). 14 | * @memberof StaticPool 15 | */ 16 | constructor(conn, poolSize = 10, driver = undefined) { 17 | 18 | let url = 'string' === typeof conn ? conn : conn.makeURL(); 19 | 20 | if (url in AvailablePool === false) { 21 | AvailablePool[url] = []; 22 | } 23 | if (url in InUsePool === false) { 24 | InUsePool[url] = []; 25 | } 26 | 27 | /** @type {string} */ 28 | this.__url = url; 29 | /** @type {number} */ 30 | this.__poolSize = poolSize; 31 | /** @type {class} */ 32 | this.__driver = driver; 33 | /** @type {Array} */ 34 | this.__available = AvailablePool[this.__url]; 35 | /** @type {Array} */ 36 | this.__inUse = InUsePool[this.__url] 37 | 38 | while (this.Size > this.Count) { 39 | this.__addConnection(); 40 | } 41 | } 42 | 43 | /** 44 | * @private 45 | * @memberof StaticPool 46 | */ 47 | __addConnection() { 48 | let connection = new PooledConnection(this, this.__url, this.__driver); 49 | this.__available.push(connection); 50 | } 51 | 52 | /** 53 | * @param {PooledConnection} connection 54 | * @private 55 | * @memberof StaticPool 56 | */ 57 | __closeConnection(connection) { 58 | let index = this.__inUse.indexOf(connection); 59 | if (index >= 0) { 60 | this.__inUse.splice(index, 1); 61 | this.__available.push(connection); 62 | } 63 | } 64 | 65 | /** 66 | * The number of available connections in the pool 67 | * @returns {number} 68 | * @readonly 69 | * @memberof StaticPool 70 | */ 71 | get Available() { 72 | return this.__available.length; 73 | } 74 | 75 | /** 76 | * The number of used connections in the pool 77 | * @returns {number} 78 | * @readonly 79 | * @memberof StaticPool 80 | */ 81 | get InUse() { 82 | return this.__inUse.length; 83 | } 84 | 85 | /** 86 | * The total number of connections in the pool 87 | * @returns {number} 88 | * @readonly 89 | * @memberof StaticPool 90 | */ 91 | get Count() { 92 | return this.Available + this.InUse; 93 | } 94 | 95 | /** 96 | * The preferred number of connections in the pool 97 | * @returns {number} 98 | * @readonly 99 | * @memberof StaticPool 100 | */ 101 | get Size() { 102 | return this.__poolSize; 103 | } 104 | 105 | /** 106 | * Grabs an available connection from the pool 107 | * 108 | * @returns {PooledConnection} 109 | * @memberof StaticPool 110 | */ 111 | getConnection() { 112 | if (this.__available.length <= 0) { 113 | this.__addConnection(); 114 | } 115 | let connection = this.__available.pop(); 116 | this.__inUse.push(connection); 117 | return connection; 118 | } 119 | 120 | /** 121 | * Closes the underlying connections and empties the pool 122 | * 123 | * @returns {Promise>} 124 | * @memberof StaticPool 125 | */ 126 | close() { 127 | /** @type {Array>} */ 128 | let promises = this.__available.concat(this.__inUse).map(el => el.kill()); 129 | return Promise.all(promises).then(values => { 130 | while (this.InUse > 0) { this.__inUse.pop(); } 131 | while (this.Available > 0) { this.__available.pop(); } 132 | }); 133 | } 134 | } 135 | 136 | module.exports = StaticPool; -------------------------------------------------------------------------------- /lib/connection.js: -------------------------------------------------------------------------------- 1 | var ConnectionObject = require('./connectionObject'); 2 | var ParseConnection = require('./parseConnection'); 3 | var Statement = require('./statement'); 4 | var PreparedStatement = require('./preparedStatement'); 5 | 6 | 7 | class Connection { 8 | 9 | constructor(conn, driver) { 10 | this.__base = 'string' === typeof conn ? ParseConnection(conn, driver) : ConnectionObject.fromPlain(conn, driver); 11 | this.__connection = this.__base.Driver.open(this.__base); 12 | this.__connectionUrl = 'string' === typeof conn ? conn : this.__base.makeURL(); 13 | this.__driver = driver || this.__base.Driver; 14 | } 15 | 16 | /** 17 | * @readonly 18 | * @returns {string} 19 | * @memberof Connection 20 | */ 21 | get URL() { 22 | return this.__connectionUrl; 23 | } 24 | 25 | /** 26 | * @readonly 27 | * @returns {object} 28 | * @memberof Connection 29 | */ 30 | get Driver() { 31 | return this.__driver; 32 | } 33 | 34 | /** 35 | * @readonly 36 | * @private 37 | * @returns {object} 38 | * @memberof Connection 39 | */ 40 | get base() { 41 | return this.__base; 42 | } 43 | 44 | /** 45 | * @readonly 46 | * @private 47 | * @returns {object} 48 | * @memberof Connection 49 | */ 50 | get connection() { 51 | return this.__connection; 52 | } 53 | 54 | /** 55 | * Creates a statement with the given SQL. 56 | * 57 | * @param {string} sql 58 | * @returns {Statement} 59 | * @memberof Connection 60 | */ 61 | createStatement(sql) { 62 | return new Statement(this, sql); 63 | } 64 | 65 | /** 66 | * Creates and prepares a statement with the given SQL. 67 | * 68 | * @param {string} sql 69 | * @returns {PreparedStatement} 70 | * @memberof Connection 71 | */ 72 | prepareStatement(sql) { 73 | return new PreparedStatement(this, sql); 74 | } 75 | 76 | /** 77 | * Closes the underlying connection. 78 | * 79 | * @returns {Promise} 80 | * @memberof Connection 81 | */ 82 | close() { 83 | return new Promise((resolve, reject) => { 84 | if (typeof this.connection.close !== 'function') { 85 | return resolve(true); // nothing to do 86 | } 87 | this.connection.close() 88 | .then(() => resolve(true)) 89 | .catch(reason => reject(reason)); 90 | }); 91 | } 92 | 93 | /** 94 | * Indicates whether the underlying driver support transactions. 95 | * 96 | * @returns {boolean} 97 | * @memberof Connection 98 | */ 99 | isTransactionSupported() { 100 | if (typeof this.connection.isTransactionSupported === 'function') { 101 | return this.connection.isTransactionSupported(); 102 | } 103 | return false; 104 | } 105 | 106 | /** 107 | * Returns true if the underlying driver is in a transaction, false otherwise. 108 | * 109 | * @returns {boolean} 110 | * @memberof Connection 111 | */ 112 | inTransaction() { 113 | if (this.isTransactionSupported()) { 114 | return this.connection.inTransaction(); 115 | } 116 | return false; 117 | } 118 | 119 | /** 120 | * Returns a boolean promise: true if a transaction was started and 121 | * false if it was not started. Transactions can fail to start if 122 | * another transaction is already running or if the driver does 123 | * not support transactions. 124 | * 125 | * @returns {Promise} 126 | * @memberof Connection 127 | */ 128 | beginTransaction() { 129 | if (! this.isTransactionSupported()) { 130 | return Promise.resolve(false); 131 | } 132 | return this.connection.beginTransaction(); 133 | } 134 | 135 | /** 136 | * Returns a boolean promise: true if a transaction was committed and 137 | * false if one was not committed. Transactions can fail to commit if 138 | * no transaction was started, or if the driver does not support 139 | * transactions. 140 | * 141 | * @returns {Promise} 142 | * @memberof Connection 143 | */ 144 | commit() { 145 | if (! this.inTransaction()) { 146 | return Promise.resolve(false); 147 | } 148 | return this.connection.commit(); 149 | } 150 | 151 | 152 | /** 153 | * Returns a boolean promise: true if a transaction was rolled back and 154 | * false if one was not rolled back. Transactions can fail to roll back if 155 | * no transaction was started, or if the driver does not support 156 | * transactions. 157 | * 158 | * @returns {Promise} 159 | * @memberof Connection 160 | */ 161 | rollback() { 162 | if (! this.inTransaction()) { 163 | return Promise.resolve(false); 164 | } 165 | return this.connection.rollback(); 166 | } 167 | } 168 | 169 | module.exports = Connection; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # database-js 2 | 3 | [![Build Status](https://travis-ci.org/mlaanderson/database-js.svg?branch=master)](https://travis-ci.org/mlaanderson/database-js) 4 | [![npm version](https://badge.fury.io/js/database-js.svg)](https://badge.fury.io/js/database-js) 5 | [![Mentioned in Awesome Node.js](https://awesome.re/mentioned-badge.svg)](https://github.com/sindresorhus/awesome-nodejs) 6 | ![downloads](https://img.shields.io/npm/dw/database-js) 7 | 8 | > Wrapper for multiple databases with a JDBC-like connection 9 | 10 | Database-js implements a common, promise-based interface for SQL database access. Inspired by JDBC, it uses connection strings to identify the database driver. Wrappers around native database drivers provide a unified interface to handle databases. Thus, you can change the target database by modifying the connection string. 😉 11 | 12 | Database-js has built-in prepared statements, even if the underlying driver does not support them. It is built on Promises, so it works well with ES7 async code. 13 | 14 | ## Contents 15 | 16 | * [Install](#install) 17 | * [Usage](#usage) 18 | * [Examples](//github.com/mlaanderson/database-js/wiki/Examples) 19 | * [API](//github.com/mlaanderson/database-js/wiki/API) 20 | * [Drivers](//github.com/mlaanderson/database-js/wiki/Drivers) 21 | * [In the Browser](//github.com/mlaanderson/database-js/wiki/Browsers) 22 | 23 | ## Install 24 | 25 | ```shell 26 | npm install database-js 27 | ``` 28 | 29 | ## Drivers 30 | 31 | | Driver (wrapper) | Note | Installation | 32 | | ---------------- | ---- | ------------ | 33 | | [ActiveX Data Objects](//github.com/mlaanderson/database-js-adodb) | *Windows only* | `npm i database-js-adodb` | 34 | | [CSV files](//github.com/mlaanderson/database-js-csv) | | `npm i database-js-csv` | 35 | | [Excel files](//github.com/mlaanderson/database-js-xlsx) | | `npm i database-js-xlsx` | 36 | | [Firebase](//github.com/mlaanderson/database-js-firebase) | | `npm i database-js-firebase` | 37 | | [INI files](//github.com/mlaanderson/database-js-ini) | | `npm i database-js-ini` | 38 | | [JSON files](//github.com/thiagodp/database-js-json) | | `npm i database-js-json` | 39 | | [MySQL](//github.com/mlaanderson/database-js-mysql) | prior to MySQL v8 | `npm i database-js-mysql` | 40 | | [MySQL2](//github.com/esteban-serfe/database-js-mysql2/) | MySQL v8+ | `npm i database-js-mysql2` | 41 | | [MS SQL Server](https://github.com/thiagodp/database-js-mssql) | | `npm i database-js-mssql` | 42 | | [PostgreSQL](//github.com/mlaanderson/database-js-postgres) | | `npm i database-js-postgres` | 43 | | [SQLite3](//github.com/thiagodp/database-js-sqlite3) | | `npm i database-js-sqlite3` | 44 | | [SQLite](//github.com/mlaanderson/database-js-sqlite) | | `npm i database-js-sqlite` | 45 | 46 | [See here](//github.com/mlaanderson/database-js/wiki/Drivers#implementing-a-new-driver) how to add a new driver. 47 | 48 | ## Usage 49 | 50 | Usage _without_ async/await: 51 | 52 | ```javascript 53 | var Connection = require('database-js').Connection; 54 | 55 | // CONNECTION 56 | var conn = 57 | new Connection("sqlite:///path/to/test.sqlite"); // SQLite 58 | // new Connection("mysql://user:password@localhost/test"); // MySQL 59 | // new Connection("postgres://user:password@localhost/test"); // PostgreSQL 60 | // 👉 Change the connection string according to the database driver 61 | 62 | // QUERY 63 | var stmt1 = conn.prepareStatement("SELECT * FROM city WHERE name = ?"); 64 | stmt1.query("New York") 65 | .then( function (results) { 66 | console.log(results); // Display the results 67 | } ).catch( function (reason) { 68 | console.log(reason); // Some problem while performing the query 69 | } ); 70 | 71 | // COMMAND 72 | var stmt2 = conn.prepareStatement("INSERT INTO city (name, population) VALUES (?, ?)"); 73 | stmt2.execute("Rio de Janeiro", 6747815) 74 | .then( function() { console.log( 'Inserted.' ); } ) 75 | .catch( function(reason) { console.log('Error: ' + reason); } ); 76 | 77 | // ANOTHER COMMAND 78 | var stmt3 = conn.prepareStatement("UPDATE city SET population = population + ? WHERE name = ?"); 79 | stmt3.execute(1, "Rio de Janeiro") 80 | .then( function() { console.log( 'Updated.' ); } ) 81 | .catch( function(reason) { console.log('Error: ' + reason); } ); 82 | 83 | // CLOSING THE CONNECTION 84 | conn.close() 85 | .then( function() { console.log('Closed.'); } ) 86 | .catch( function(reason) { console.log('Error: ' + reason); } ); 87 | ``` 88 | 89 | ### Async / await 90 | 91 | Using async/await: 92 | 93 | ```javascript 94 | const Connection = require('database-js').Connection; 95 | 96 | (async () => { 97 | let conn; 98 | try { 99 | // CONNECTION 100 | conn = new Connection('mysql://user:password@localhost/test'); 101 | 102 | // QUERY 103 | const stmt1 = conn.prepareStatement('SELECT * FROM city WHERE name = ?'); 104 | const results = await stmt1.query('New York'); 105 | console.log(results); 106 | 107 | // COMMAND 1 108 | const stmt2 = conn.prepareStatement('INSERT INTO city (name, population) VALUES (?,?)'); 109 | await stmt1.execute('Rio de Janeiro', 6747815); 110 | 111 | // COMMAND 2 112 | const stmt2 = conn.prepareStatement('UPDATE city SET population = population + ? WHERE name = ?'); 113 | await stmt1.execute(1, 'Rio de Janeiro'); 114 | } catch (reason) { 115 | console.log(reason); 116 | } finally { 117 | try { 118 | await conn.close(); 119 | } catch (err) { 120 | console.log(err); 121 | } 122 | } 123 | })(); 124 | ``` 125 | 126 | ## Basic API 127 | 128 | ```ts 129 | class Connection { 130 | 131 | /** Creates and prepares a statement with the given SQL. */ 132 | prepareStatement(sql: string): PreparedStatement; 133 | 134 | /** Closes the underlying connection. */ 135 | close(): Promise; 136 | 137 | /** Indicates whether the underlying driver support transactions. */ 138 | isTransactionSupported(): boolean; 139 | 140 | /** Returns true if the underlying driver is in a transaction, false otherwise. */ 141 | inTransaction(): boolean; 142 | 143 | /** 144 | * Starts a transaction (if supported). 145 | * 146 | * Transactions can fail to start if another transaction is already running or 147 | * if the driver does not support transactions. 148 | */ 149 | beginTransaction(): Promise; 150 | 151 | /** 152 | * Commits a transaction (if supported). 153 | * 154 | * Transactions can fail to commit if no transaction was started, or if the driver 155 | * does not support transactions. 156 | */ 157 | commit(): Promise; 158 | 159 | /** 160 | * Cancels a transaction (if supported). 161 | * 162 | * Transaction can fail to be rolled back no transaction was started, or if the driver 163 | * does not support transactions. 164 | */ 165 | rollback(): Promise; 166 | } 167 | ``` 168 | 169 | ```ts 170 | class PreparedStatement { 171 | /** 172 | * Performs the prepared SQL query with the given arguments. 173 | * Returns a Promise with an array of rows. 174 | */ 175 | query(...args: any): Promise>; 176 | 177 | /** Executes the prepared SQL statement with the given arguments. */ 178 | execute(... args): Promise; 179 | } 180 | ``` 181 | 182 | 183 | ## See also 184 | 185 | - [Wiki](https://github.com/mlaanderson/database-js/wiki) for more examples and how to use a connection pool. 186 | 187 | - [codeceptjs-dbhelper](https://github.com/thiagodp/codeceptjs-dbhelper) - Allows to use [database-js](https://github.com/mlaanderson/database-js) inside [CodeceptJS](https://github.com/codeception/codeceptjs/) tests (as a helper). 188 | 189 | 190 | ## License 191 | 192 | [MIT](LICENSE) 193 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace databasejs { 2 | 3 | interface ConnectionStruct { 4 | driverName?: string; 5 | DriverName?: string; 6 | username?:string; 7 | Username?:string; 8 | password?:string; 9 | Password?:string; 10 | hostname?:string; 11 | Hostname?:string; 12 | port?:string; 13 | Port?:string; 14 | database?:string; 15 | Database?:string; 16 | parameters?:string; 17 | Parameters?:string; 18 | } 19 | 20 | class ConnectionObject { 21 | constructor(driverName: string, username: string, password: string, hostname: string, port: string, database: string, driver?: any); 22 | 23 | /** Splits the parameter string into an object of key/value pairs */ 24 | parseParameters() : Object; 25 | 26 | /** Makes a URL from this ConnectionObject */ 27 | makeURL() : string; 28 | 29 | /** Allows plain object to be used to construct a ConnectionObject */ 30 | static fromPlain(obj: ConnectionStruct, driver?: any): ConnectionObject; 31 | } 32 | 33 | class Statement { 34 | /** 35 | * Executes the SQL query. If any parameters are required, they 36 | * will be passed to the query here. 37 | * 38 | * @param {any[]} args arguments to replace into the prepared SQL string 39 | * @returns {Promise} 40 | * @memberof Statement 41 | */ 42 | query(... args: any[]) : Promise>; 43 | 44 | /** 45 | * Prepares the statement for use with parameters. 46 | * 47 | * @memberof Statement 48 | */ 49 | prepare() : void; 50 | 51 | /** 52 | * Executes the SQL statement. If any parameters are required, they 53 | * will be passed in here. 54 | * 55 | * @param {any[]} args arguments to replace into the prepared SQL string 56 | * @returns {Promise} 57 | * @memberof Statement 58 | */ 59 | execute(... args: any[]) : Promise>; 60 | } 61 | 62 | class PreparedStatement extends Statement {} 63 | 64 | class Connection { 65 | constructor(url: string | ConnectionStruct, driver?: any); 66 | 67 | readonly URL: string; 68 | readonly Driver: Object; 69 | 70 | /** 71 | * Creates a statement with the passed SQL. 72 | * 73 | * @param {string} sql the SQL string to use for the statement 74 | * @returns {Statement} a Statement object 75 | * @memberof Connection 76 | */ 77 | createStatement(sql: string) : Statement; 78 | 79 | /** 80 | * Creates and prepares a statement with the passed SQL. 81 | * 82 | * @param {string} sql the SQL string to use for the statement 83 | * @returns {PreparedStatement} a PreparedStatement object 84 | * @memberof Connection 85 | */ 86 | prepareStatement(sql: string) : PreparedStatement; 87 | 88 | /** 89 | * Closes the underlying connection. 90 | * 91 | * @returns {Promise} 92 | * @memberof Connection 93 | */ 94 | close() : Promise; 95 | 96 | /** 97 | * Indicates whether the underlying driver can support transactions; 98 | * 99 | * @returns {boolean} 100 | * @memberof Connection 101 | */ 102 | isTransactionSupported() : boolean; 103 | 104 | /** 105 | * Returns true if the underlying driver is in a transaction, false 106 | * if it does not support transactions or is not in a transaction. 107 | * 108 | * @returns {boolean} 109 | * @memberof Connection 110 | */ 111 | inTransaction() : boolean; 112 | 113 | /** 114 | * Returns a boolean promise: true if a transaction was started and 115 | * false if it was not started. Transactions can fail to start if 116 | * another transaction is already running or if the driver does 117 | * not support transactions. 118 | * 119 | * @returns {Promise} 120 | * @memberof Connection 121 | */ 122 | beginTransaction() : Promise; 123 | 124 | /** 125 | * Returns a boolean promise: true if a transaction was committed and 126 | * false if one was not committed. Transactions can fail to commit if 127 | * no transaction was started, or if the driver does not support 128 | * transactions. 129 | * 130 | * @returns {Promise} 131 | * @memberof Connection 132 | */ 133 | commit() : Promise; 134 | 135 | /** 136 | * Returns a boolean promise: true if a transaction was rolled back and 137 | * false if one was not rolled back. Transactions can fail to roll back if 138 | * no transaction was started, or if the driver does not support 139 | * transactions. 140 | * 141 | * @returns {Promise} 142 | * @memberof Connection 143 | */ 144 | rollback() : Promise; 145 | } 146 | 147 | class PooledConnection extends Connection { 148 | /** 149 | * Closes the connection completely 150 | * 151 | * @returns {Promise} 152 | * @memberof PooledConnection 153 | */ 154 | kill() : Promise; 155 | 156 | /** 157 | * Frees this connection for the pool 158 | * 159 | * @returns {Promise} 160 | * @memberof PooledConnection 161 | */ 162 | close() : Promise; 163 | } 164 | 165 | interface Pool { 166 | /** 167 | * The number of used connections in the pool 168 | * @returns {number} 169 | * @readonly 170 | * @memberof Pool 171 | */ 172 | readonly Available : number; 173 | 174 | /** 175 | * The number of used connections in the pool 176 | * @returns {number} 177 | * @readonly 178 | * @memberof Pool 179 | */ 180 | readonly InUse : number; 181 | 182 | /** 183 | * The total number of connections in the pool 184 | * @returns {number} 185 | * @readonly 186 | * @memberof Pool 187 | */ 188 | readonly Count : number; 189 | 190 | /** 191 | * The prefered number of connections in the pool 192 | * @returns {number} 193 | * @readonly 194 | * @memberof Pool 195 | */ 196 | readonly Size : number; 197 | 198 | /** 199 | * Grabs an available connection from the pool 200 | * 201 | * @returns {PooledConnection} 202 | * @memberof Pool 203 | */ 204 | getConnection() : PooledConnection; 205 | 206 | /** 207 | * Closes the underlying connections and empties the pool 208 | * 209 | * @returns {Promise>} 210 | * @memberof Pool 211 | */ 212 | close() : Promise> 213 | } 214 | 215 | class StaticPool implements Pool { 216 | constructor(url: string | ConnectionObject, poolSize: number, driver?: any); 217 | 218 | /** 219 | * The number of used connections in the pool 220 | * @returns {number} 221 | * @readonly 222 | * @memberof Pool 223 | */ 224 | readonly Available : number; 225 | 226 | /** 227 | * The number of used connections in the pool 228 | * @returns {number} 229 | * @readonly 230 | * @memberof Pool 231 | */ 232 | readonly InUse : number; 233 | 234 | /** 235 | * The total number of connections in the pool 236 | * @returns {number} 237 | * @readonly 238 | * @memberof Pool 239 | */ 240 | readonly Count : number; 241 | 242 | /** 243 | * The prefered number of connections in the pool 244 | * @returns {number} 245 | * @readonly 246 | * @memberof Pool 247 | */ 248 | readonly Size : number; 249 | 250 | /** 251 | * Grabs an available connection from the pool 252 | * 253 | * @returns {PooledConnection} 254 | * @memberof Pool 255 | */ 256 | getConnection() : PooledConnection; 257 | 258 | /** 259 | * Closes the underlying connections and empties the pool 260 | * 261 | * @returns {Promise>} 262 | * @memberof Pool 263 | */ 264 | close() : Promise> 265 | } 266 | 267 | class DynamicPool implements Pool { 268 | constructor(url: string | ConnectionObject, poolSize: number, driver?: any); 269 | 270 | /** 271 | * The number of used connections in the pool 272 | * @returns {number} 273 | * @readonly 274 | * @memberof Pool 275 | */ 276 | readonly Available : number; 277 | 278 | /** 279 | * The number of used connections in the pool 280 | * @returns {number} 281 | * @readonly 282 | * @memberof Pool 283 | */ 284 | readonly InUse : number; 285 | 286 | /** 287 | * The total number of connections in the pool 288 | * @returns {number} 289 | * @readonly 290 | * @memberof Pool 291 | */ 292 | readonly Count : number; 293 | 294 | /** 295 | * The prefered number of connections in the pool 296 | * @returns {number} 297 | * @readonly 298 | * @memberof Pool 299 | */ 300 | readonly Size : number; 301 | 302 | /** 303 | * Grabs an available connection from the pool 304 | * 305 | * @returns {PooledConnection} 306 | * @memberof Pool 307 | */ 308 | getConnection() : PooledConnection; 309 | 310 | /** 311 | * Closes the underlying connections and empties the pool 312 | * 313 | * @returns {Promise>} 314 | * @memberof Pool 315 | */ 316 | close() : Promise> 317 | } 318 | 319 | } 320 | 321 | export = databasejs; --------------------------------------------------------------------------------