diff --git a/app.js b/app.js index c5a6d536e42c5b24729d06720924ad8a0b5059e3..8356a3e877374652cf1da59e3ffbbfae2ab286bb 100644 --- a/app.js +++ b/app.js @@ -6,6 +6,7 @@ // User Imports. const config = require('./config.js'); +const { generate_users, test_db_functions } = require('./src/database_functions.js'); const { userConfirmation } = require('./src/helper_functions.js'); const { Logging, testLogLevels } = require('./src/logging.js'); const MySql = require('./src/mysql.js'); @@ -22,8 +23,6 @@ async function main() { logger.info('Starting program.'); logger.log(''); - // testLogLevels(logger); - // Create MySql helper class. var debug = true; const mysql = new MySql(config['mysql'], debug); @@ -31,24 +30,16 @@ async function main() { // Connect to database. await mysql.open_conn(); - // Test SHOW TABLES query. - let table_list = await mysql.show_tables(); - - // Test DESCRIBE table query. - for (let index = 0; index < table_list.length; index++) { - await mysql.describe_table(table_list[index]); - } - - // Test SELECT * query. - for (let index = 0; index < table_list.length; index++) { - await mysql.query('SELECT * FROM {};'.format(table_list[index])); + // Generate database users. + for (let index = 0; index < 1000; index++) { + generate_users(mysql); } // Reset database by emptying all tables. - // mysql.reset_db(); + // await mysql.reset_db(true); // Close existing database connections. - mysql.close_conn(); + await mysql.close_conn(); logger.log(''); logger.info('Terminating program.'); diff --git a/src/database_functions.js b/src/database_functions.js new file mode 100644 index 0000000000000000000000000000000000000000..6b8b13c423f1bd407d16cd32ed9a39b4d068fdf6 --- /dev/null +++ b/src/database_functions.js @@ -0,0 +1,73 @@ +/** + * Various functions to call on database. + * Separated from app.js to simulate calling database functions across multiple files. + */ + +// System Imports. +const mysql = require('mysql2/promise'); + +// User Imports. +const { userConfirmation } = require('./helper_functions.js'); +const { Logging } = require('./logging.js'); + + +// Initialize logging. +let logger = Logging.init('./logging/'); + + +/** + * Basic logic to test db functions. + * :param mysql: Instance of MySQL connector class. + * :param debug: Bool indicating if debug mode or not. + */ +async function test_db_functions(mysql, debug) { + if (debug) { + logger.log(''); + logger.debug('Executing `test_db_functions()` function:'); + } + + // Test SHOW TABLES query. + let table_list = await mysql.show_tables(); + + // Test DESCRIBE table query. + for (let index = 0; index < table_list.length; index++) { + await mysql.describe_table(table_list[index]); + } + + // Test SELECT * query. + for (let index = 0; index < table_list.length; index++) { + await mysql.query('SELECT * FROM {};'.format(table_list[index])); + } +} + + +/** + * Generates initial database users. + * :param mysql: Instance of MySQL connector class. + * :param debug: Bool indicating if debug mode or not. + */ +async function generate_users(mysql, debug) { + if (debug) { + logger.log(''); + logger.debug('Executing `generate_users()` function:'); + } + + mysql.query('INSERT INTO user (name) VALUES (?);', 'Bob'); + mysql.query('INSERT INTO user (name) VALUES (?);', 'Johnny'); + mysql.query('INSERT INTO user (name) VALUES (?);', 'Sarah'); + mysql.query('INSERT INTO user (name) VALUES (?);', 'Jenny'); + mysql.query('INSERT INTO user (name) VALUES (?);', 'Olivia'); + mysql.query('INSERT INTO user (name) VALUES (?);', 'Jessie'); + mysql.query('INSERT INTO user (name) VALUES (?);', 'Mark'); + mysql.query('INSERT INTO user (name) VALUES (?);', 'Sally'); + mysql.query('INSERT INTO user (name) VALUES (?);', 'Timmy'); + mysql.query('INSERT INTO user (name) VALUES (?);', 'Liz'); + mysql.query('INSERT INTO user (name) VALUES (?);', 'Anthony'); + mysql.query('INSERT INTO user (name) VALUES (?);', 'Tiffany'); +} + + +module.exports = { + generate_users, + test_db_functions, +}; diff --git a/src/helper_functions.js b/src/helper_functions.js index efa0fbcc0b637012fee65a22ffbd3ff7f44200eb..16d2f06c8af92d1082addbece924ed0ce620d50a 100644 --- a/src/helper_functions.js +++ b/src/helper_functions.js @@ -71,7 +71,17 @@ function userConfirmation() { } +/** + * Custom equivalent of "sleep" in other languages, as per https://stackoverflow.com/a/39914235. + */ +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + + + module.exports = { getCurrentDate, + sleep, userConfirmation, }; diff --git a/src/logging.js b/src/logging.js index 0e258ddff1a56b7f3527b29598174eda732256d4..66065170a34aa5075ee7187f9d8df073d960a97b 100644 --- a/src/logging.js +++ b/src/logging.js @@ -281,7 +281,7 @@ class LoggingClass { // // Generate and print Console text. let prefix = ` ${chalk.magenta('[SQL Q]')} [${relativeFileName} ${lineNum}] `; - let console_string = prefix.concat('Query: ', firstArgument, ...otherArguments); + let console_string = prefix.concat(firstArgument, ...otherArguments); console.log(console_string); // Generate and print Log File text. diff --git a/src/mysql.js b/src/mysql.js index eeb6b5cf5f387b2149058539cc89e49f01dd0005..80d027d3837e495e8ef45c5e49487c58c33d7222 100644 --- a/src/mysql.js +++ b/src/mysql.js @@ -6,7 +6,7 @@ const mysql = require('mysql2/promise'); // User Imports. -const { userConfirmation } = require('./helper_functions.js'); +const { userConfirmation, sleep } = require('./helper_functions.js'); const { Logging } = require('./logging.js'); @@ -27,7 +27,7 @@ class MySql { // Save class variables. this.config = config; this.debug = debug; - this.connection = null; + this.connection_pool = null; } /** @@ -40,48 +40,76 @@ class MySql { } // Check if connection is currently open. - if (this.connection != null) { + if (this.connection_pool != null) { // Connection currently exists. Close so we can start a new one. logger.warn('Connection already exist. Closing old connection first.'); this.close_conn(); } // Open MySQL connection. - const connection = await mysql.createPool({ + const connection_pool = await mysql.createPool({ host: this.config['host'], port: this.config['port'], database: this.config['database'], user: this.config['user'], password: this.config['password'], - waitForConnections: true, - connectionLimit: 10, - queueLimit: 0 + charset: 'UTF8MB4_GENERAL_CI', + connectionLimit: 10, // Max connetions to db before queuing further requests. + waitForConnections: true, // Ensures connections queue if connectLimit is reached. + queueLimit: 0 // Max connections able to queue at once. }); logger.info('Connection pool has been created.'); - // Save connection to class. - this.connection = connection; + // Save connection pool to class. + this.connection_pool = connection_pool; - return connection; + return connection_pool; } /** * Closes current MySql connection. */ - close_conn() { + async close_conn() { if (this.debug) { logger.log(''); logger.debug('Closing MySql connection...'); } - // Check if connection is currently open. - if (this.connection != null) { - // Connection currently exists. Safe to close. - this.connection.end(); - logger.info('Connection has been terminated.'); + // Check if connection pool is currently open. + if (this.connection_pool != null) { + // Connection pool currently exists. Check if associated connections are still running. + + let safe_to_close = false; + while (! safe_to_close) { + logger.log('') + logger.info('Attempting to close connection pool.'); + let total_conns = this.connection_pool.pool['_allConnections']['_list'].length; + let free_conns = this.connection_pool.pool['_freeConnections']['_list'].length; + let conn_queue_head = this.connection_pool.pool['_connectionQueue']['_head']; + let conn_queue_tail = this.connection_pool.pool['_connectionQueue']['_tail']; + if (this.debug) { + console.debug('total_connections: {}'.format(total_conns)); + console.debug('free_connections: {}'.format(free_conns)); + console.debug('con_queue: {}, {}'.format(conn_queue_head, conn_queue_tail)); + } + + // Check if connections are open and connection queue is empty. + if ( total_conns == free_conns && conn_queue_head == conn_queue_tail) { + // No connections in use. Save to close pool. + safe_to_close = true; + this.connection_pool.end(); + logger.info('Connection pool has been terminated.'); + } else { + // One or more queries still need to execute. Wait 5 seconds and try again. + logger.warning('Connection pool is in use. Trying again in 5 seconds...'); + await sleep(5000); + } + } + + } else { - // Connection does not yet exist. Cannot close. - logger.warn('There is no connection to close.'); + // Connection pool does not yet exist. Cannot close. + logger.warn('There is no connection pool to close.'); } } @@ -95,9 +123,15 @@ class MySql { } // Execute query. - let query = 'SHOW TABLES;'; - logger.sql_q(query); - let [rows, fields] = await this.connection.query(query); + const connection = await this.connection_pool.getConnection(); + let rows; let fields; + try { + let query = 'SHOW TABLES;'; + logger.sql_q(query); + [rows, fields] = await connection.query(query); + } finally { + connection.release(); + } // Check results. if (rows.length < 1) { @@ -130,9 +164,15 @@ class MySql { } // Execute query. - let query = 'DESCRIBE {};'.format(table_name); - logger.sql_q(query); - let [rows, fields] = await this.connection.query(query); + const connection = await this.connection_pool.getConnection(); + let rows; let fields; + try { + let query = 'DESCRIBE {};'.format(table_name); + logger.sql_q(query); + [rows, fields] = await connection.query(query); + } finally { + connection.release(); + } // Check results. if (rows.length < 1) { @@ -159,8 +199,14 @@ class MySql { } // Execute query. - logger.sql_q(query_string); - let [rows, fields] = await this.connection.query(query_string, query_values); + const connection = await this.connection_pool.getConnection(); + let rows; let fields; + try { + logger.sql_q('Query: {} Values: {}'.format(query_string, query_values)); + [rows, fields] = await connection.query(query_string, query_values); + } finally { + connection.release(); + } // Check results. if (rows.length < 1) { @@ -190,7 +236,7 @@ class MySql { if (skip_confirmation) { response_bool = true; } else { - response_bool = userConfirmation(); + response_bool = await userConfirmation(); } // Only proceed if user accepts. @@ -198,9 +244,16 @@ class MySql { // User accepted. logger.info('Resetting all database tables...'); - table_list = this.show_tables(); - for (let index = 0; index < table_list.length; index++) { - this.query('TRUNCATE {};'.format(table_list[index])); + let table_list = await this.show_tables(); + const connection = await this.connection_pool.getConnection(); + try { + for (let index = 0; index < table_list.length; index++) { + let query = 'TRUNCATE {};'.format(table_list[index]); + logger.sql_q(query); + await this.query(query); + } + } finally { + connection.release(); } logger.info('Database tables reset.');