Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 136 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@ Same as query but resolves an empty array if no records found.

'use strict';

var mysql = require('mysql'),
config = require('config'),
dbPool = mysql.createPool(config.mysql)
const mysql = require('mysql'),
config = require('config'),
dbPool = mysql.createPool(config.mysql);

var DB = require('alien-node-mysql-utils')(dbPool),
validateAccountData = require('../some-validator');
const DB = require('alien-node-mysql-utils')(dbPool),
validateAccountData = require('../some-validator');

var createAndExecuteQuery = function(status) {
var query = 'SELECT * FROM accounts WHERE status = ?',
queryStatement = [query, [status]];
const createAndExecuteQuery = (status) => {
const query = 'SELECT * FROM accounts WHERE status = ?',
queryStatement = [query, [status]];

return DB.query(queryStatement);
};
Expand All @@ -48,8 +48,8 @@ var createAndExecuteQuery = function(status) {
* @param {Number} status
* @returns {Promise}
*/
function getAccountsByStatus(status) {
validateAccountData({status : status});
const getAccountsByStatus = status => {
validateAccountData({ status });
return createAndExecuteQuery(status);
}

Expand All @@ -62,12 +62,12 @@ module.exports = getAccountsByStatus;

```js

var getAccountsByStatus = require('../models/getAccountsByStatus');
const getAccountsByStatus = require('../models/getAccountsByStatus');

getAccountsByStatus('active').then(function(accounts) {
getAccountsByStatus('active').then(accounts => {
// handle array of accounts here
})
.catch(function(err) {
.catch(err => {
// handle "No records found" or other errors here
});

Expand All @@ -77,12 +77,12 @@ getAccountsByStatus('active').then(function(accounts) {

```js

var getAccountsByStatus = require('../models/getAccountsByStatus');
const getAccountsByStatus = require('../models/getAccountsByStatus');

getAccountsByStatus('active').then(function(maybeAccounts) {
getAccountsByStatus('active').then(maybeAccounts => {
// handle array of accounts or empty array here
})
.catch(function(err) {
.catch(err => {
// handle errors here
});

Expand All @@ -99,16 +99,16 @@ Same as lookup, but resolves `undefined` if no records are found.

'use strict';

var mysql = require('mysql'),
config = require('config'),
dbPool = mysql.createPool(config.mysql)
const mysql = require('mysql'),
config = require('config'),
dbPool = mysql.createPool(config.mysql);

var DB = require('alien-node-mysql-utils')(dbPool),
validateAccountData = require('../some-validator');
const DB = require('alien-node-mysql-utils')(dbPool),
validateAccountData = require('../some-validator');

var createAndExecuteQuery = function(id) {
var query = 'SELECT * FROM accounts WHERE id = ?',
queryStatement = [query, [id]];
const createAndExecuteQuery = id => {
const query = 'SELECT * FROM accounts WHERE id = ?',
queryStatement = [query, [id]];

return DB.lookup(queryStatement);
};
Expand All @@ -118,8 +118,8 @@ var createAndExecuteQuery = function(id) {
* @param {Number} id
* @returns {Promise}
*/
function getAccountById(id) {
validateAccountData({id : id});
const getAccountById = id => {
validateAccountData({ id });
return createAndExecuteQuery(id);
}

Expand All @@ -132,13 +132,13 @@ module.exports = getAccountById;

```js

var getAccountById = require('../models/getAccountById');
const getAccountById = require('../models/getAccountById');


getAccountById(1234).then(function(account) {
getAccountById(1234).then(account => {
// handle account object here
})
.catch(function(err) {
.catch(err => {
// handle "No records found" or other errors here
});

Expand All @@ -148,17 +148,121 @@ getAccountById(1234).then(function(account) {

```js

var getAccountById = require('../models/getAccountById');
const getAccountById = require('../models/getAccountById');


getAccountById(1234).then(function(maybeAccount) {
getAccountById(1234).then(maybeAccount => {
// handle account object or undefined here
})
.catch(function(err) {
.catch(err => {
// handle errors here
});

```

## Transactions
This library supports some simple transaction abstractions to play nicely with your promise chains.

The three methods you need to care about are :
- DB.beginTransaction()
- DB.addQueryToTransaction()
- DB.commit()

These methods have a unique signature compared to the other methods for querying. Let's break them down:

**DB.beginTransaction()** : `() -> Promise(connection)`

This method will use the curried `dbPool` object provided during require...

```js
const DB = require('alien-node-mysql-utils')(dbPool);
```

... and call the native `getConnection()` on it, then resolve the connection on its promise.

This connection needs to be provided to the subsequent methods so the transaction knows how to commit and rollback.

**DB.addQueryToTransaction()** : `connection -> query -> Promise({ data, connection })`

This method accepts the connection object which you should have gotten from `DB.beginTransaction()`, along with the typical query which you give to
any other query method in this library. It behaves like `DB.querySafe()` in that it lets you
deal with all the data scrubbing and null-checks (resolves zero-or-more result sets and all `SELECT` statements
return an array).

Please notice that this method returns the connection along with the data, so in the spirit of
keeping the unary promise chain data flow in mind, the promise will resolve a single object,
where the data lives in a `data` property, and the connection on a `connection` property.

**DB.commit()** : `connection`

This method accepts the connection object which you should have gotten from `DB.beginTransaction()`. It simply
resolves `true` if there are no errors, otherwise it rejects the promise with whatever error may happen to ruin your day.

##### Suggested wrapper-model usage for transactions

```js
const DB = require('alien-node-mysql-utils')(dbPool);

const getUserBalance = id => connection => {
const query = 'SELECT balance FROM users WHERE id = ?',
queryStatement = [query, [id]];

return DB.addQueryToTransaction(connection, queryStatement);
};

const updateUserBalance = (id, amount) => connection => {
const query = 'UPDATE users SET balance = balance + ? WHERE id = ?',
queryStatement = [query, [amount, id]];

return DB.addQueryToTransaction(connection, queryStatement);
};

const ensurePositiveTransfer = amount => connection => {
if (amount > 0) {
return connection;
} else {
throw {
error : new Error('What are you doing?' ),
connection : transaction.connection
};
};
};

const ensureEnoughMoney = amount => transaction => {
const data = transaction.data || [{ balance : 0 }],
balance = data[0].balance || 0;

if (amount <= balance) {
return transaction;
} else {
throw {
error : new Error('Broke ass' ),
connection : transaction.connection
};
}
};

const senderUserId = 1234,
receiverUserId = 5678,
amountToSend = 500.45;

const resolveConnection = o => o.connection;

DB.beginTransaction()
.then(ensurePositiveTransfer(amountToSend))
.then(getUserBalance(senderUserId))
.then(ensureEnoughMoney(amountToSend))
.then(resolveConnection)
.then(updateUserBalance(senderUserId, amountToSend * -1))
.then(resolveConnection)
.then(updateUserBalance(receiverUserId, amountToSend))
.then(resolveConnection)
.then(DB.commit)
.catch(exception => {
exception.connection.rollback();
logger.error(exception.error);
});

```
## TODO
- Make the transform to/from column methods unbiased with decorator injection
6 changes: 5 additions & 1 deletion lib/DB.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
'use strict';

var DB = function(dbPool) {
const DB = dbPool => {
return {
resolveOrRejectOnBooleanField : require('./methods/resolveOrRejectOnBooleanField'),
beginTransaction : require('./methods/beginTransaction')(dbPool),
getConnection : require('./methods/getConnection')(dbPool),
query : require('./methods/query')(dbPool),
querySafe : require('./methods/querySafe')(dbPool),
addQueryToTransaction : require('./methods/addQueryToTransaction'),
commit : require('./methods/commit'),
lookup : require('./methods/lookup')(dbPool),
lookupSafe : require('./methods/lookupSafe')(dbPool),
fuzzify : require('./methods/fuzzify'),
Expand Down
8 changes: 6 additions & 2 deletions lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,21 @@ module.exports = {
code : 6001,
message : 'Cannot connect to MySQL. Make sure the database is running.'
},
DUPLICATE : function(message) {
DUPLICATE : (message) => {
return {
code : 6002,
message : message
};
},
UNKNOWN : function(message) {
UNKNOWN : (message) => {
return {
code : 6999,
message : message
};
},
MISSING_CONNECTION : {
code : 6998,
message : 'There was a problem establishing a database connection. This is likely an application error and not a MySQL error.'
}
}
};
23 changes: 14 additions & 9 deletions lib/methods/_connectionHandle.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
'use strict';

var R = require('ramda');
const _queryCallback = require('./_queryCallback'),
constants = require('../constants');

var _queryCallback = require('./_queryCallback'),
constants = require('../constants');
const _connectionHandle = (deferred, queryStatement, transaction, singleReturnItem, allowEmptyResponse) => (err, connection) => {

var _connectionHandle = R.curry(function(deferred, queryStatement, singleReturnItem, allowEmptyResponse, err, connection) {

var preparedStatement = queryStatement[0],
valueSwapIns = queryStatement[1];
const preparedStatement = queryStatement[0],
valueSwapIns = queryStatement[1];

if (!connection) {
deferred.reject(constants.errors.NO_DB_CONNECTION);
}

if (err) {

if (transaction) {
connection.rollback();
}

connection.release();
deferred.reject(err);
}

connection.query(
preparedStatement,
valueSwapIns,
_queryCallback(deferred, connection, singleReturnItem, allowEmptyResponse)
_queryCallback(deferred, connection, transaction, singleReturnItem, allowEmptyResponse)
);

});
return deferred.promise;
};

module.exports = _connectionHandle;
Loading