any-db-adapter-spec
Advanced tools
Comparing version 0.0.1 to 2.0.2
{ | ||
"name": "any-db-adapter-spec", | ||
"version": "0.0.1", | ||
"version": "2.0.2", | ||
"description": "Specification and test suite for any-db adapters", | ||
@@ -5,0 +5,0 @@ "main": "test-any-db-adapter.js", |
204
README.md
@@ -12,32 +12,32 @@ # Any-DB API | ||
## Objects | ||
## Interfaces | ||
- [Connection](#connection) | ||
- [Query](#query) | ||
- [Transaction][] (external link) | ||
- [Queryable][] - a common interface implemented by connections, pools, and | ||
transactions. | ||
- [Connection][] - the "transport" responsible for getting SQL queries to a | ||
database, and streaming results back through a `Query` instance. | ||
- [Query][] - a [Readable][] stream that emits row objects. | ||
## Connection | ||
## Queryable | ||
```ocaml | ||
Connection := EventEmitter & { | ||
adapter: String, | ||
query: (text: String, params: Array?, Continuation<Results>?) => Query, | ||
begin: (statement: String?, Continuation<Transaction>?) => Transaction, | ||
end: (Continuation<void>?) => void | ||
Queryable := EventEmitter & { | ||
adapter: Adapter | ||
query: (text: String, params: Array?, Continuation<Results>?) => Query | ||
query: (Query) => Query | ||
} | ||
``` | ||
Connection objects are obtained using [createConnection][] from [Any-DB][] or | ||
[ConnectionPool.acquire][], both of which delegate to the | ||
[createConnection](#adaptercreateconnection) implementation of the specified | ||
adapter. | ||
Known implementations: | ||
- [Connection][Connection] | ||
- [ConnectionPool][ConnectionPool] (external) | ||
- [Transaction][Transaction] (external). | ||
`Connection` instances are guaranteed to have the methods and events listed | ||
here, but drivers (and their adapters) may have additional methods or emit | ||
additional events. If you need to access a feature of your database that is not | ||
described here (such as Postgres' server-side prepared statements), consult the | ||
documentation for the database driver. | ||
### Queryable.adapter | ||
### Connection.query | ||
The [Adapter][] instance that will be used by this `Queryable` for creating | ||
[Query][] instances and/or connections. | ||
### Queryable.query | ||
```ocaml | ||
@@ -49,5 +49,6 @@ (text: String, params: Array?, Continuation<ResultSet>?) => Query | ||
Execute a SQL statement using bound parameters (if they are provided) and | ||
return a [Query](#query) object for the in-progress query. If a | ||
`Continuation<Results>` is provided it will be passed a [ResultSet](#resultset) | ||
object after the query has completed. | ||
return a [Query][] object that is a [Readable][] stream of the resulting | ||
rows. If a `Continuation<Results>` is provided the rows returned by the | ||
database will be aggregated into a [ResultSet][] which will be passed to the | ||
continuation after the query has completed. | ||
@@ -60,3 +61,3 @@ The second form is not needed for normal use, but must be implemented by | ||
```javascript | ||
conn.query('SELECT * FROM my_table', function (err, res) { | ||
queryable.query('SELECT * FROM my_table', function (err, res) { | ||
if (err) return console.error(err) | ||
@@ -68,59 +69,46 @@ res.rows.forEach(console.log) | ||
*EventEmitter-style* | ||
*Stream-style* | ||
```javascript | ||
conn.query('SELECT * FROM my_table') | ||
queryable.query('SELECT * FROM my_table') | ||
.on('error', console.error) | ||
.on('row', console.log) | ||
.on('data', console.log) | ||
.on('end', function () { console.log('All done!') }) | ||
``` | ||
### Connection.begin | ||
### Queryable events | ||
- `'query', query` - Emitted immediately before a query is executed. | ||
## Connection | ||
```ocaml | ||
(statement: String?, Continuation<Transaction>?) => Transaction | ||
Connection := Queryable & { | ||
end: (Continuation<void>?) => void | ||
} | ||
``` | ||
Start a new database transaction and return a [Transaction][] to manage it. If | ||
`statement` is given it will be used in place of the default statement | ||
(`BEGIN`). If a `Continuation` is given it will be called after the database | ||
transaction has successfully started (or failed to do so). | ||
Known implementations: | ||
*Note for adapter authors*: This method is trivially implemented by delegating | ||
to [Transaction.begin][] from [any-db-transaction][] . | ||
- [any-db-mysql][] (external) | ||
- [any-db-postgres][] (external) | ||
- [any-db-sqlite3][] (external) | ||
*Callback-style* | ||
```javascript | ||
conn.begin(function (err, transaction) { | ||
if (err) return console.error(err) | ||
// Do work using transaction | ||
transaction.query(...) | ||
transaction.commit() | ||
}) | ||
``` | ||
Connection objects are obtained using [createConnection][] from [Any-DB][] or | ||
[ConnectionPool.acquire][], both of which delegate to the | ||
[createConnection](#adaptercreateconnection) implementation of the specified | ||
adapter. | ||
*Synchronous-style* | ||
```javascript | ||
var transaction = conn.begin() | ||
transaction.on('error', console.error) | ||
// Do work using transaction, queries are queued until transaction successfully | ||
// starts. | ||
transaction.query(...) | ||
transaction.commit() | ||
``` | ||
While all `Connection` objects implement the [Queryable][] interface, the | ||
implementations in each adapter may add additional methods or emit additional | ||
events. If you need to access a feature of your database that is not described | ||
here (such as Postgres' server-side prepared statements), consult the | ||
documentation for your adapter. | ||
See also: the [Transaction][] API docs. | ||
### Connection.end | ||
`conn.end([callback])` | ||
`(Continuation<void>) => void` | ||
Close the database connection. If `callback` is given it will be called after | ||
the connection has closed. | ||
Close the database connection. If a continuation is provided it will be | ||
called after the connection has closed. | ||
### Connection.adapter | ||
`conn.adapter` | ||
Contains the adapter name used for this connection, e.g. `'sqlite3'`, etc. | ||
### Connection Events | ||
@@ -134,3 +122,3 @@ | ||
```ocaml | ||
Query := EventEmitter & { | ||
Query := Readable<Object> & { | ||
text: String, | ||
@@ -141,7 +129,14 @@ values: Array | ||
Query objects are returned by the `query` methods of [connections][Connection.query], | ||
[pools][ConnectionPool.query], and [transactions][Transaction.query]. Like | ||
connections, query objects are created by an adapter and may have more methods | ||
and events than are described here. | ||
`Query` objects are returned by the [Queryable.query][Queryable.query] method, | ||
available on [connections][Connection], [pools][ConnectionPool.query], and | ||
[transactions][Transaction.query]. Queries are instances of [Readable][], and | ||
as such can be [piped][Readable.pipe] through transforms and support backpressure | ||
for more efficient memory-usage on very large results sets. (Note: at this time | ||
the `sqlite3` driver does not support backpressure) | ||
Internally, `Query` instances are | ||
[created by a database Adapter][Adapter.createQuery] and may have more methods, | ||
properties, and events than are described here. Consult the documentation for | ||
your specific adapter to find out about any extensions. | ||
### Query.text | ||
@@ -158,6 +153,29 @@ | ||
* `'error', err` - Emitted if the query results in an error. | ||
* `'row', row` - Emitted for each row in the queries result set. | ||
* `'end', [res]` - Emitted when the query completes. | ||
* `'error', error` - Emitted if the query results in an error. | ||
* `'fields', fields` - An array of [Field][ResultSet] objects emitted before | ||
any `'data'` events. | ||
* `'data', row` - Emitted for each row in the query result set. | ||
* `'end'` - Emitted when the query completes. | ||
## ResultSet | ||
```ocaml | ||
ResultSet := { | ||
fields: Array<Field> | ||
rows: Array<Object<Any>> | ||
rowCount: Integer | ||
lastInsertId: Any? | ||
} | ||
Field := { | ||
name: String | ||
{* other properties are driver specific *} | ||
} | ||
``` | ||
`ResultSet` objects are just plain data that collect results of a query when a | ||
continuation is provided to [Queryable.query][]. The `lastInsertId` is optional, | ||
and currently supported by `sqlite3` and `mysql` but not `postgres`, because | ||
it is not supported by Postgres itself. | ||
## Adapter | ||
@@ -167,4 +185,5 @@ | ||
Adapter: { | ||
createConnection: (Object, Continuation<Connection>?) => Connection, | ||
createQuery: (String, Array?, Continuation<Results>?) => Query, | ||
name: String | ||
createConnection: (Object, Continuation<Connection>?) => Connection, | ||
createQuery: (String, Array?, Continuation<Results>?) => Query, | ||
} | ||
@@ -176,2 +195,6 @@ ``` | ||
### Adapter.name | ||
The string name of the adapter, e.g. `'mysql'`, `'postgres'` or `'sqlite3'`. | ||
### Adapter.createConnection | ||
@@ -195,6 +218,6 @@ | ||
Create a [Query](#query) that may be executed later on by a [Connection][]. | ||
While this function is rarely needed by user code, it makes it possible for | ||
[ConnectionPool.query][] and [Transaction.query][] to return a [Query][] object | ||
synchronously in the same style as a [Connection.query][]. | ||
Create a [Query](#query) that *may* eventually be executed later on by a | ||
[Connection][]. While this function is rarely needed by user code, it makes | ||
it possible for [ConnectionPool.query][] and [Transaction.query][] to fulfill | ||
the [Queryable.query][] contract by synchronously returning a [Query][] stream. | ||
@@ -207,16 +230,25 @@ # License | ||
[once]: http://npm.im/once | ||
[test suite]: tests | ||
[parse-db-url]: https://github.com/grncdr/parse-db-url#api | ||
[any-db]: https://github.com/grncdr/node-any-db | ||
[any-db-mysql]: https://github.com/grncdr/node-any-db-mysql | ||
[any-db-postgres]: https://github.com/grncdr/node-any-db-postgres | ||
[any-db-sqlite3]: https://github.com/grncdr/node-any-db-sqlite3 | ||
[createConnection]: https://github.com/grncdr/node-any-db#exportscreateconnection | ||
[parse-db-url]: https://github.com/grncdr/parse-db-url#api | ||
[Connection]: #connection | ||
[Connection.query]: #connectionquery | ||
[Query]: #query | ||
[Readable]: http://nodejs.org/api/stream.html#stream_class_stream_readable | ||
[Readable.pipe]: http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options | ||
[ConnectionPool.query]: https://github.com/grncdr/node-any-db-pool#connectionpoolquery | ||
[ConnectionPool.acquire]: https://github.com/grncdr/node-any-db-pool#connectionpoolacquire | ||
[ConnectionPool]: https://github.com/grncdr/node-any-db-pool#api | ||
[Transaction]: https://github.com/grncdr/node-any-db-transaction | ||
[Transaction]: https://github.com/grncdr/node-any-db-transaction#api | ||
[any-db-transaction]: https://github.com/grncdr/node-any-db-transaction | ||
[Transaction.begin]: https://github.com/grncdr/node-any-db-transaction#transactionquery | ||
[Transaction.query]: https://github.com/grncdr/node-any-db-transaction#transactionquery | ||
[test suite]: tests | ||
[Queryable]: #queryable | ||
[Queryable.query]: #queryablequery | ||
[Connection]: #connection | ||
[Connection.query]: #connectionquery | ||
[Query]: #query | ||
[Adapter.createQuery]: #adaptercreatequery |
@@ -1,3 +0,3 @@ | ||
require('../test').withConnection("Last insert id", function (conn, t) { | ||
if (conn.adapter == 'postgres') { | ||
require('../test')("Last insert id", function (conn, t) { | ||
if (conn.adapter.name == 'postgres') { | ||
t.skip("Last insert ID not supported by postgres") | ||
@@ -8,20 +8,21 @@ return t.end() | ||
debugger | ||
conn.query("DROP TABLE last_insert_id_test", function (err) {}) | ||
if (conn.adapter == 'sqlite3') | ||
if (conn.adapter.name == 'sqlite3') | ||
conn.query("CREATE TABLE last_insert_id_test (id integer primary key autoincrement, a int)") | ||
else if (conn.adapter == 'mysql') | ||
conn.query("CREATE TABLE last_insert_id_test (id integer primary key auto_increment, a int)") | ||
else if (conn.adapter.name == 'mysql') | ||
{ | ||
conn.query("CREATE TABLE last_insert_id_test (id integer primary key auto_increment, a int)").on('end', function () { console.log('create complete') }) | ||
} | ||
else throw new Error("Unknown adapter: " + conn.adapter.name) | ||
conn.query('INSERT INTO last_insert_id_test (a) VALUES (123)', function (err, res) { | ||
if (err) throw err | ||
t.equal(res.lastInsertId, 1) | ||
t.equal(res.lastInsertId, 1) | ||
conn.query('INSERT INTO last_insert_id_test (a) VALUES (456)', function (err, res) { | ||
if (err) throw err | ||
t.equal(res.lastInsertId, 2) | ||
t.equal(res.lastInsertId, 2) | ||
}) | ||
}) | ||
} | ||
) | ||
}) |
@@ -1,11 +0,11 @@ | ||
require('../test').withTransaction("Streaming results", function (tx, test) { | ||
require('../test')("Streaming results", function (conn, test) { | ||
test.plan(11) | ||
tx.query("DROP TABLE IF EXISTS streaming_test", function (err) { /* swallow errors */ }) | ||
tx.query("CREATE TABLE streaming_test (a int)") | ||
conn.query("DROP TABLE IF EXISTS streaming_test", function (err) { /* swallow errors */ }) | ||
conn.query("CREATE TABLE streaming_test (a int)") | ||
var placeHolder = tx.adapter == 'postgres' ? '($1)' : '(?)'; | ||
var placeHolder = conn.adapter.name == 'postgres' ? '($1)' : '(?)'; | ||
var vals = [] | ||
for (var i = 0; i < 10; i++) { | ||
tx.query('INSERT INTO streaming_test (a) VALUES ' + placeHolder, [i]) | ||
conn.query('INSERT INTO streaming_test (a) VALUES ' + placeHolder, [i]) | ||
vals.push(i) | ||
@@ -15,7 +15,9 @@ } | ||
var i = 0; | ||
tx.query('SELECT a FROM streaming_test') | ||
.on('row', function (row) { test.equal(row.a, vals.shift()) }) | ||
.on('end', function (result) { | ||
conn.query('SELECT a FROM streaming_test') | ||
.on('data', function (row) { | ||
test.equal(row.a, vals.shift()) | ||
}) | ||
.on('end', function () { | ||
test.deepEqual(vals, []) | ||
}) | ||
}) |
var tape = require('tape') | ||
var test = module.exports = tape.bind(null) | ||
test.withAdapter = function (description, callback) { | ||
test(description, function (t) { | ||
var config = require('../config') | ||
callback(config, t) | ||
}) | ||
} | ||
test.withConnection = maybeOpts(function (description, opts, callback) { | ||
module.exports = function test (description, opts, callback) { | ||
if (!callback) { | ||
callback = opts | ||
opts = {} | ||
} | ||
tape(description, function (t) { | ||
@@ -21,30 +16,2 @@ var config = require('../config') | ||
}) | ||
}) | ||
test.withTransaction = maybeOpts(function (description, opts, callback) { | ||
test(description, function (t) { | ||
var config = require('../config') | ||
config.adapter.createConnection(config.url, function (err, conn) { | ||
if (err) throw err | ||
var tx = conn.begin() | ||
t.on('end', function () { | ||
if (tx.state() != 'closed') { | ||
tx.rollback(conn.end.bind(conn)) | ||
} else { | ||
conn.end() | ||
} | ||
}) | ||
callback(tx, t) | ||
}) | ||
}) | ||
}) | ||
function maybeOpts(f) { | ||
return function (description, opts, callback) { | ||
if (!callback) { | ||
callback = opts | ||
opts = {} | ||
} | ||
f(description, opts, callback) | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
2
244
14656
11
161
1