I'm writing a function which makes an insert using mssql every time it is called. The function received 2 params (action, user), which is the data inserted into the SQL Server table.
Function looks like this:
function saveActionToDB(action, user) {
if (config.Logging.DB.type == 'mssql') {
const dbOptions = {
user: config.Logging.DB.user,
password: config.Logging.DB.password,
server: config.Logging.DB.server,
database: config.Logging.DB.database,
options: {
encrypt: config.Logging.DB.encrypt
}
};
const database = require('mssql');
const emmitQuery = async () => {
try {
const pool = await database.connect(dbOptions);
const request = pool.request();
request.input('action', sql.VarChar, action);
request.input('user', sql.VarChar, user);
request.query('insert into actions (action, user) values (#action, #user)', (err, result)=>{
//if (err) combinedLogger.error(err);
});
}
catch (err) {
combinedLogger.error(err);
}
}
emmitQuery();
}
else if(config.Logging.DB.type == 'oracle') {
//oracle
}
}
However, I am getting a console error with "undefined", which means that the try is catching an error, however, the err it's undefined, so I can't figure out what the error is.
Can anyone help?
Related
I want to mock a SQL Server connection.
Here is the code to be tested:
import sql from 'mssql';
export class DBConnection {
public async query(input: any): Promise<any> {
const config = {
server: "server",
port: 1234,
database: "db",
user: "user",
password: "password",
trustServerCertificate: true
};
try {
const pool = new sql.ConnectionPool(config);
await pool.connect();
let request = pool.request();
request.input('key', sql.VarChar, input.key);
request.input('value', sql.VarChar, input.value);
let insertQuery = "INSERT INTO SOMETABLE (key, value) OUTPUT INSERTED.key VALUES (#key, #value)"
return await request.query(insertQuery);
} catch (err) {
console.error(err);
}
}
}
My question is: how to create mock of sql.ConnectionPool? I tried using jest.Mocked to mock entire module, tried using jest.spy to mock the constructor or specific method, but seems none of them working
Here is the mock test, apparently not working
import { DBConnection } from './db';
import sql from 'mssql';
jest.mock('mssql');
const mockpool = sql as jest.Mocked<typeof sql>;
const pool = mockpool.ConnectionPool;
let db = new DBConnection();
describe('DB test', () => {
beforeEach(() => {
jest.spyOn(sql.ConnectionPool.prototype, 'connect').mockImplementation(() => pool);
});
afterEach(() => {
jest.resetAllMocks();
});
it('should have success response', async () => {
const input = { key: 'key1', value: 1234 }
try {
const response = await db.query(input);
expect(response).toBe("key1");
} catch (e) {
}
});
});
export async function insertAttempt() {
const db = await openDatabase()
try {
return await new Promise((resolve, reject) => {
db.transaction(
(tx) => {
tx.executeSql("INSERT INTO Attempt (attempt_date)
VALUES (?)", [Date.now()])
tx.executeSql(query.selectAttempt, [], (transaction, resultSet) => {
console.log(resultSet)
})
},
reject,
resolve
)
})
} catch (e) {
console.log("Error: ", e)
}
}
I'm calling the above in a react hook component, like so:
useEffect(() => {
async function example() {
await insertAttempt()
}
example()
}, [])
Error: [Error: attempt to write a readonly database (code 1032 SQLITE_READONLY_DBMOVED)]
I don't have this problem with select sql operations, only with inserts.
If you are using a Pre-populated database in your App, go to your openDatabase() function, you may need to access the private attribute _db of WebSQLDatabase and close it with available method, you will end up with something similar to below.
export default async function openDatabase() {
const database = SQLite.openDatabase("myDb.db")
database._db.close()
if (!(await FileSystem.getInfoAsync(FileSystem.documentDirectory + "SQLite")).exists) {
await FileSystem.makeDirectoryAsync(FileSystem.documentDirectory + "SQLite");
}
await FileSystem.downloadAsync(
Asset.fromModule(require("../assets/www/myDb.db")).uri,
FileSystem.documentDirectory + "SQLite/myDb.db"
);
return SQLite.openDatabase("myDb.db");
}
When I make a "GET" request from the client to the server the server should make a axios.get() call to a stock API to retrieve data for an array of tickers. When I console.log the results it seems to be working fine but the array doesn't seem to save, like it gets wiped out and comes back to the client as empty. I think I might be messing this up with async/await.
async function currentPrice(ticker) {
const apiURL = `https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=${ticker}&apikey=${API_KEY}`;
let price;
await axios.get(apiURL).then(data => {
try {
price = data.data["Global Quote"]["05. price"];
} catch (error) {
console.log(error)
}
})
return price;
};
app.get("/refresh", redirectLogin, (req, res) => {
const {
user
} = res.locals;
var array = [];
connection.query(`SELECT * FROM holdings WHERE user_name = '${user.user_name}' AND quantity > 0`, (err, results) => {
if (err) throw err;
results.forEach(holding => {
currentPrice(holding.ticker).then(data => {
var updatedTicker = {
ticker: holding.ticker,
description: holding.description,
price_acquired: holding.price_acquired,
market_price: data,
delta: parseFloat(this.market_price) - parseFloat(this.price_acquired),
quantity: holding.quantity,
trade_date: holding.date_acquired
}
array.push(updatedTicker);
// console.log(array);
console.log(updatedTicker.market_price)
})
})
res.json(array)
})
})
You are calling res.json(array) before any of your currentPrice().then(...) calls have finished, thus the array is still empty.
There are a number of different ways to solve this. Probably the simplest is to change for .forEach() loop to a plain for loop and then use async/await to serialize each of your calls to currentPrice():
function currentPrice(ticker) {
const apiURL = `https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=${ticker}&apikey=${API_KEY}`;
return axios.get(apiURL).then(data => {
try {
return data.data["Global Quote"]["05. price"];
}
catch (error) {
console.log(error);
throw error;
}
});
}
app.get("/refresh", redirectLogin, (req, res) => {
const { user } = res.locals;
connection.query(`SELECT * FROM holdings WHERE user_name = '${user.user_name}' AND quantity > 0`, async (err, results) => {
if (err) {
console.log(err);
res.sendStatus(500);
return;
}
try {
const array = [];
for (let holding of results) {
let data = await currentPrice(holding.ticker);
let updatedTicker = {
ticker: holding.ticker,
description: holding.description,
price_acquired: holding.price_acquired,
market_price: data,
delta: parseFloat(this.market_price) - parseFloat(this.price_acquired),
quantity: holding.quantity,
trade_date: holding.date_acquired
}
array.push(updatedTicker);
}
res.json(array);
} catch(e) {
console.log(e);
res.sendStatus(500);
}
});
});
Various changes:
Simplified the currentPrice() function to just return the axios promise directly
Appropriately reject in currentPrice() if there's an error so the caller sees the error.
Add proper error handling (sending an error response), if the db query fails.
Switch .forEach() loop to a for loop so we can use await to serialize the calls to currentPrice() so we can more easily know when they are all done.
Add error handling and sending of an error response if currentPrice() has an error.
Call res.json(array) only after all the now-serialized calls to await currentPrice() have completed.
FYI, a fully complete transformation here would switch to mysql2 so you can use the promise interface for connection.query() rather than the plain callback interface that you are using now. That would allow you to consolidate error handling to one place more easily.
Please help I'm trying to deploy my app to App Engine/CloudSQL but I keep getting :
"UnhandledPromiserejectWarning": Cannot enqueue after fatal error..
I'm trying to query MySQL as promise, when I don't I handle the exception it works fine locally, but when I deploy it doesn't work.
How can I handle promise rejection, please Help Thanks
This is db.js
const db = require('./Mysql')
const query = (q, data) => {
return new Promise((resolve, reject) => {
db.query(q, data, (err, res) => (err ? reject(err) : resolve(res)))
})
.then(res => console.log(res))
.catch(err => console.error(err))
This is Mysql.js
{ SQL_SOCKET, SQL_USER, SQL_PASSWORD, SQL_DATABASE } = process.env
const db = mysql.createConnection({
socketPath: SQL_SOCKET,
user: SQL_USER,
password: SQL_PASSWORD,
database: SQL_DATABASE,
charset: 'utf8mb4',
})
module.exports = db
I remember having this problem a few years ago when I tried using the mysql module within an expressJS application and attempted to use async/await. The error could also come from querying on a connection in which a fatal error occured, see here. As such, best practices dictates that on queries, you open a connection, start a query transaction, commit the query and then release the connection afterwards -- allowing you to rollback whenever an error occurs. I do not see this process happening here so it could be a possibility.
In any case, I can provide you with an alternative, which is the method I ended up going with. Basically, I digressed promisifying query() myself and instead let node handle it.
An example of using query without transaction:
/backend/database/database.js
const mysql = require('mysql');
const db = require('../config/config').mysql;
const util = require('util');
const pool = mysql.createPool({
connectionLimit: require('../config/config').cLimit,
host: db.host,
user: db.user,
password: db.password,
database: db.database
});
pool.query = util.promisify(pool.query);
async function query(cmd) {
try {
let result = await pool.query(cmd);
return result;
} catch(error) {
console.error(error);
}
}
module.exports = {
query
};
Which can then be used in your models like such:
/backend/models/User.js
const db = require('../database/database');
async function getUserById(userId) {
const cmd = `SELECT * FROM Users WHERE userId = ${userId}`;
try {
let result = await db.query(cmd);
return result;
} catch(error) {
throw {
message: error
}
}
}
module.exports = {
getUserById
};
Which in turn, can be called from your route handler like such:
/backend/routes/users.js
const router = require('express').Router();
const User = require('../models/User');
router.get('/getUserById/:userId', async (req, res) => {
try {
let user = await User.getUserById(req.params.userId);
res.status(200).send({ user });
} catch(error) {
console.error(error);
res.status(400).send({ error: 'Unable to fetch user' });
}
});
module.exports = router;
I am using this code to connect to my SQL Server and retrieve some data which works fine, if I only call the code once. If I call it twice I get this error:
ConnectionError: Already connecting to database! Call close before connecting to different database.at ConnectionPool._connect
But I am closing the conn after the call so I'm not sure what I am missing.
var sql = require('mssql');
const pool = new sql.ConnectionPool({
user: 'sa',
password: 'password',
server: '192.168.1.2',
database: 'demo',
options: {
encrypt: false
}
})
var conn = pool;
module.exports.getCounter = function( query){
conn.connect().then(function (err) {
var req = new sql.Request(conn);
req.query(query).then(function (result) {
console.log(result.recordset);
return result.recordset;
conn.close();
})
.catch(function (err) {
console.log(err);
conn.close();
});
})
.catch(function (err) {
console.log(err);
})};
You're returning the value before closing the connection, hence the function terminates before reaching that line. So just move the return statement below your conn.close(). The other issues you might have afterwards is that you might be calling your function twice before one executes and terminates completely, since those calls are asynchronous.
You might have to set your getCounter function as a Promise, so that you can wait for its completion/failure before calling it again. Off the top of my head in your example:
const getCounter = () => new Promise((resolve,reject) => {
conn.connect().then(function (err) {
var req = new sql.Request(conn);
req.query(query).then(function (result) {
conn.close();
resolve(result);
})
.catch(function (err) {
conn.close();
reject(err);
});
})
})
You can call your function afterwards as getCounter().then((result) => {...})
Here is another way to solve it which might be helpful for others.
const sql = require('mssql')
let connectionPoolConfig = {
user: 'sa',
password: 'password',
server: '192.168.1.2',
database: 'demo',
options: {
encrypt: false
}
}
let connectionPoolPromise = null
let connectionPoolObj = null
let getOrCreatePool = async () => {
if (connectionPoolObj) {
return connectionPoolObj
} else if (!connectionPoolPromise) {
connectionPoolPromise = new sql.ConnectionPool(connectionPoolConfig).connect()
}
connectionPoolObj = await connectionPoolPromise
return connectionPoolObj
}
let query = async(sql) => {
const pool = await getOrCreatePool()
return await pool.request().query(sql)
}
module.exports = {
query: query
}
And here is how to call it
let testCallerSQL = async () => {
try {
const res = await sqlUtil.query('select * from mytable')
console.log(res.recordset)
} catch(err) {
console.log(err)
} finally {
}
}