Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

any-db

Package Overview
Dependencies
Maintainers
1
Versions
33
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

any-db - npm Package Compare versions

Comparing version 0.1.0 to 0.1.1

test/auto_rollback.test.js

8

DESIGN.md

@@ -191,5 +191,5 @@ # Purpose

**Responsibility:** Wrap a `ConnectionAdapter` and return it to the connection pool
when the transaction is either committed or rolled back. Unhandled query errors will
be emitted by the transaction object itself and cause an automatic rollback.
**Responsibility:** Wrap a `ConnectionAdapter` and so that all queries take place
within a single database transaction. Unhandled query errors will be emitted by
the transaction object itself and cause an automatic rollback.

@@ -202,3 +202,3 @@ ### Interface

except queries are guaranteed to be performed within the transaction. If the
transaction is committed or rolled back calls to `query` will fail.
transaction has been committed or rolled back calls to `query` will fail.
* `commit([callback])` - Issue a `COMMIT` statement to the database. If

@@ -205,0 +205,0 @@ `callback` is given it will be called with any errors (or `null`) after the

@@ -44,5 +44,17 @@ var EventProxy = require('../event-proxy')

MySQL.prototype.query = helpers.queryMethod(function (stmt, params, callback, qa) {
var query = this._connection.query(stmt, params)
var queryArgs, queryAdapter
try {
queryArgs = helpers.fixPlaceholders(stmt, params, '?')
} catch (err) {
if (callback) return callback(err)
else process.nextTick(function () {
// next tick to ensure we have a QueryAdapter instance
queryAdapter.emit('error', err)
})
}
var query = this._connection.query(queryArgs[0], queryArgs[1])
var res = []
return QueryAdapter.wrap(qa, query, {
queryAdapter = QueryAdapter.wrap(qa, query, {
error: function (err) { callback ? callback(err) : this.emit('error', err) },

@@ -58,2 +70,4 @@ result: function (row) {

})
return queryAdapter
})

@@ -60,0 +74,0 @@

@@ -52,4 +52,3 @@ var EventProxy = require('../event-proxy')

SQLite3.prototype.query = helpers.queryMethod(function (stmt, params, callback, qa) {
var args = [stmt, params]
, res = []
var args, res = []

@@ -59,2 +58,11 @@ if (!qa) qa = new QueryAdapter

try {
args = helpers.fixPlaceholders(stmt, params, '?')
} catch (err) {
if (callback) callback(err)
else process.nextTick(qa.emit.bind(qa, 'error', err))
return
}
function handleError (err) {

@@ -65,5 +73,7 @@ if (callback) callback(err)

var _errored = false;
args.push(
function handleRow (err, row) {
if (err) return handleError(err)
if (_errored) return
if (err) return _errored = true
if (qa._buffer) res.push(row)

@@ -70,0 +80,0 @@ qa.emit('row', row)

@@ -27,1 +27,37 @@ var QueryAdapter = require('./query-adapter')

}
var singleQuote = /'[^']/
exports.fixPlaceholders = function (stmt, params, placeholder) {
if (!placeholder) placeholder = '?'
var _params = []
var inQuotes = false
stmt = stmt.split("'").map(function (chunk) {
if (inQuotes) {
if (chunk) inQuotes = !inQuotes // empty chunk means we had a double '
return chunk
} else {
inQuotes = !inQuotes
return chunk.replace(/\$([\a\d]+)/g, function (m) {
var name = m[1]
if (Array.isArray(params)) name = parseInt(name) - 1
if (!params.hasOwnProperty(name)) {
throw new Error("Parameter " + name + " not present in params")
}
_params.push(params[name])
return placeholder
})
}
}).join("'")
return [stmt, _params]
}
if (require.main === module) {
var fixPlaceholders = exports.fixPlaceholders
console.log(fixPlaceholders('SELECT blah FROM nah WHERE a = $1', [10]))
console.log(fixPlaceholders('INSERT INTO streaming_test (a) VALUES ($1)', [10]))
console.log(fixPlaceholders('SELECT blah FROM nah WHERE a = $1', [10], '$'))
console.log(fixPlaceholders("SELECT blah FROM nah WHERE a = '$1'", [10], '$'))
console.log(fixPlaceholders("SELECT blah FROM nah WHERE a = 'like ''$money'''", [10], '$'))
}

@@ -14,3 +14,3 @@ module.exports = StateMachine

removeMethods = (function () {
var removeMethods = (function () {
for (var methodName in methods) {

@@ -24,3 +24,3 @@ if (currentState in methods[methodName]) delete this[methodName]

if (to === currentState) return;
if (to === currentState) return true;

@@ -27,0 +27,0 @@ var extra = Array.prototype.slice.call(arguments, 1)

@@ -40,15 +40,11 @@ var queryMethod = require("./helpers").queryMethod

function _handleError (err, callback) {
var self = this
var propagate = callback || this.emit.bind(this, 'error')
if (this.state() != 'rolled back') this.rollback(function (rollbackErr) {
if (rollbackErr) {
err = new Error('Failed to rollback transaction: ' + rollbackErr
+ '\nError causing rollback: ' + err)
+ '\nError causing rollback: ' + err)
}
if (callback) callback(err)
else self.emit('error', err)
propagate(err)
})
else process.nextTick(function () {
if (callback) callback(err)
else self.emit('error', err)
})
else process.nextTick(propagate)
}

@@ -55,0 +51,0 @@

{
"name": "any-db",
"version": "0.1.0",
"version": "0.1.1",
"description": "Database-agnostic connection pooling, querying, and result sets",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -20,8 +20,8 @@ # any-db - a less-opinionated database abstraction layer.

* Exposes a uniform transaction API.
* Uses one style of parameter placeholders (Postgres-style $n or $named) with
all drivers.
Things it will do soon:
* Optionally replace db-agnostic parameter placeholders with driver specific
ones so you can use the exact same code against all drivers.
* Have lot's and lot's of tests
* Have more and more tests.
* Provide a common result set API.

@@ -39,3 +39,2 @@

for that.
* Leave it's dishes in the sink and leave town for the weekend.

@@ -66,6 +65,19 @@ ## Usage

You can also create or get an existing connection pool with `anyDB.getPool`. It
takes the following options:
To use bound parameters simply pass an array as the second argument to query:
var pool = anyDB.getPool('postgres://user:pass@localhost/dbname', {
conn.query('SELECT * FROM users WHERE gh_username = $1', ['grncdr'])
You can also use named parameters by passing an object instead:
conn.query('SELECT * FROM users WHERE gh_username = $username', {username: 'grncdr'})
Any-db doesn't do any parameter escaping on it's own, so you can use any
advanced parameter escaping features of the underlying driver exactly as though
any-db wasn't there.
### Connection pools
You can create a connection pool with `anyDB.createPool`:
var pool = anyDB.createPool('postgres://user:pass@localhost/dbname', {
min: 5, // Minimum connections

@@ -86,31 +98,42 @@ max: 10, // Maximum connections

A connection pool has the following methods available:
A connection pool has a `query` method that acts exactly like the one on
connections, but the underlying connection is returned to the pool when the
query completes.
// Exactly like conn.query above, but the underlying connection will be
// auto-released back into the pool when the query completes.
pool.query(...)
### Transactions
Transactions can be started with `begin`, in this example we stream all users
and then apply updates based on the results from an external service:
Both connections and pools have a `begin` method that starts a new transaction
and returns a `Transaction` object. Transaction objects behave much like
connections, but instead of an `end` method, they have `commit` and `rollback`
methods. Additionally, an unhandled error emitted by a transaction query will
cause an automatic rollback of the transaction before being re-emitted by the
transaction itself.
Here's an example where we stream all of our user ids, check them against an
external abuse-monitoring service, and flag or delete users as necessary, if
for any reason we only get part way through, the entire transaction is rolled
back and nobody is flagged or deleted:
var tx = pool.begin()
tx.on('error', function (err) {
// Called for any query errors without an associated callback
tx.rollback()
finished(err)
})
tx.on('error', finished)
tx.query('SELECT id FROM users').on('row', function (user) {
/*
Why query with the pool and not the transaction?
Because it allows the transaction queries to begin executing immediately,
rather than queueing them all up behind the initial SELECT.
*/
pool.query('SELECT id FROM users').on('row', function (user) {
if (tx.state() == 'rolled back') return
externalService.method(user.id, function (err, result) {
abuseService.checkUser(user.id, function (err, result) {
if (err) return tx.handleError(err)
// Errors from these queries will propagate up to the transaction object
if (result.flag) {
tx.query('UPDATE users SET flag = 1 WHERE id = ?', [user.id])
} else if (result.deleteme) {
tx.query('DELETE FROM users WHERE id = ?', [user.id])
tx.query('UPDATE users SET abuse_flag = 1 WHERE id = $1', [user.id])
} else if (result.destroy) {
tx.query('DELETE FROM users WHERE id = $1', [user.id])
}
})
}).on('error', function (err) {
tx.handleError(err)
}).on('end', function () {

@@ -117,0 +140,0 @@ tx.commit(finished)

@@ -34,5 +34,4 @@ var anyDB = require('../')

var tx = conn.begin()
tx.driver = connString.split(':').shift()
t.on('end', tx.rollback.bind(tx, conn.end.bind(conn)))
tx.on('error', function (err) { debugger; t.emit('error', err) })
tx.on('error', function (err) { t.emit('error', err) })
callback(tx, t)

@@ -39,0 +38,0 @@ })

@@ -5,6 +5,4 @@ require('./helpers').allTransactions("Streaming results", function (tx, t) {

var vals = []
var placeholder = tx.driver == 'postgres' ? '$1' : '?'
var insert = 'INSERT INTO streaming_test (a) VALUES (' + placeholder + ')'
for (var i = 0; i < 10; i++) {
tx.query(insert, [i])
tx.query('INSERT INTO streaming_test (a) VALUES ($1)', [i])
vals.push(i)

@@ -17,4 +15,7 @@ }

t.deepEqual(vals, [])
tx.query('DROP TABLE streaming_test', t.end.bind(t))
tx.query('DROP TABLE streaming_test', function (err) {
if (err) t.emit('error', err)
else t.end()
})
})
})
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc