diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 71c7472a4..3ea68d240 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -20,6 +20,7 @@ All definitions files include a header with the author and editors, so at some p * [Angular Translate](http://pascalprecht.github.io/angular-translate/) (by [Michel Salib](https://github.com/michelsalib)) * [Angular UI Bootstrap](http://angular-ui.github.io/bootstrap) (by [Brian Surowiec](https://github.com/xt0rted)) * [any-db](https://github.com/grncdr/node-any-db) (by [Rogier Schouten](https://github.com/rogier-schouten)) +* [any-db-transaction](https://github.com/grncdr/node-any-db-transaction) (by [Rogier Schouten](https://github.com/rogier-schouten)) * [AppFramework](http://app-framework-software.intel.com/) (by [Kyo Ago](https://github.com/kyo-ago)) * [Arbiter](http://arbiterjs.com/) (by [Arash Shakery](https://github.com/arash16)) * [asciify](https://github.com/olizilla/asciify) (by [Alan](http://alan.norbauer.com)) diff --git a/any-db-transaction/any-db-transaction-tests.ts b/any-db-transaction/any-db-transaction-tests.ts new file mode 100644 index 000000000..55ca53bdc --- /dev/null +++ b/any-db-transaction/any-db-transaction-tests.ts @@ -0,0 +1,29 @@ + +/// +/// + +"use strict"; + +import anyDB = require("any-db"); +import begin = require("any-db-transaction"); + +var conn: anyDB.Connection = anyDB.createConnection("mysql://user:password@localhost/testdb"); + + +var transaction = begin(conn); +var transaction2 = begin(transaction); + +begin(conn, { autoRollback: true }); +begin(conn, (error: Error, result: begin.Transaction): void => { +}); + +transaction.query("SELECT * FROM MyTable"); + +transaction.commit(); +transaction.commit((error: Error): void => { +}); + +transaction.rollback(); +transaction.rollback((error: Error): void => { +}); + diff --git a/any-db-transaction/any-db-transaction.d.ts b/any-db-transaction/any-db-transaction.d.ts new file mode 100644 index 000000000..7f65e959d --- /dev/null +++ b/any-db-transaction/any-db-transaction.d.ts @@ -0,0 +1,93 @@ +// Type definitions for any-db-transaction 2.2.1 +// Project: https://github.com/grncdr/node-any-db-transaction +// Definitions by: Rogier Schouten +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +/// + +declare module "any-db-transaction" { + import anyDB = require("any-db"); + + module begin { + /** + * Transaction objects are are simple wrappers around a Connection that also implement the Queryable API, + * but guarantee that all queries take place within a single database transaction or not at all. Note that + * begin also understands how to acquire (and release) a connection from a ConnectionPool as well, so you + * can simply pass a pool to it: var tx = begin(pool) + * + * By default, any queries that error during a transaction will cause an automatic rollback. If a query has + * no callback, the transaction will also handle (and re-emit) 'error' events for the Query instance. + * This enables handling errors for an entire transaction in a single place. + * + * Transactions may also be nested by passing a Transaction to begin and these nested transactions can + * safely error and rollback without rolling back their parent transaction + * + * Transaction events: + * 'query', query - emitted immediately after .query is called on a connection via tx.query. The argument is a query object. + * 'commit:start' - Emitted when .commit() is called. + * 'commit:complete' - Emitted after the transaction has committed. + * 'rollback:start' - Emitted when .rollback() is called. + * 'rollback:complete' - Emitted after the transaction has rolled back. + * 'close' - Emitted after rollback or commit completes. + * 'error', err - Emitted under three conditions: + * There was an error acquiring a connection. + * Any query performed in this transaction emits an error that would otherwise go unhandled. + * Any of query, begin, commit, or rollback are called after the connection has already been committed or rolled back. + * Note that the 'error' event may be emitted multiple times! depending on the callback you are registering, you way want to wrap it using [once][]. + */ + interface Transaction extends anyDB.Queryable { + + /** + * Issue a COMMIT (or RELEASE ... in the case of nested transactions) statement to the database. + * If a continuation is provided it will be called (possibly with an error) after the COMMIT + * statement completes. The transaction object itself will be unusable after calling commit(). + */ + commit(callback?: (error: Error) => void): void; + + /** + * The same as Transaction.commit but issues a ROLLBACK. Again, the transaction will be unusable after calling this method. + */ + rollback(callback?: (error: Error) => void): void; + } + + interface TransactionOptions { + /** + * Adapter name e.g. 'mysql' + */ + adapter?: anyDB.Adapter; + /** + * SQL statement for beginning a transaction, default 'BEGIN' + */ + begin?: string; + /** + * SQL statement for committing a transaction, default 'COMMIT' + */ + commit?: string; + /** + * SQL statement for rolling back a transaction, default 'ROLLBACK' + */ + rollback?: string; + /** + * Callback for transaction + */ + callback?: (error: Error, transaction: Transaction) => void; + /** + * Rollback automatically on error, default true + */ + autoRollback?: boolean; + } + } + + /** + * Start a transaction + */ + function begin(q: anyDB.Queryable, options?: begin.TransactionOptions, callback?: (error: Error, transaction: begin.Transaction) => void): begin.Transaction; + function begin(q: anyDB.Queryable, callback?: (error: Error, transaction: begin.Transaction) => void): begin.Transaction; + function begin(q: anyDB.Queryable, beginStatement?: string, callback?: (error: Error, transaction: begin.Transaction) => void): begin.Transaction; + function begin(q: anyDB.Queryable, options?: begin.TransactionOptions, beginStatement?: string, callback?: (error: Error, transaction: begin.Transaction) => void): begin.Transaction; + + export = begin; +} + + + diff --git a/any-db/any-db-tests.ts b/any-db/any-db-tests.ts index 06a0dd2b0..15dc6c7e6 100644 --- a/any-db/any-db-tests.ts +++ b/any-db/any-db-tests.ts @@ -7,7 +7,32 @@ import anyDB = require("any-db"); var conn: anyDB.Connection = anyDB.createConnection("mysql://user:password@localhost/testdb"); var sql: string = "SELECT * FROM questions"; + +conn.query(sql, [1, "boo"]); + conn.query(sql).on("data", (row: Object[]): void => { // nothing }); + +conn.query(sql, [1, "s"], (error: Error, result: anyDB.ResultSet): void => { + result.rows.length; + result.fields.length; +}); + conn.end(); + + +var poolConfig: anyDB.PoolConfig = { + min: 1, + max: 200 +}; + +var pool: anyDB.ConnectionPool = anyDB.createPool("mysql://user:password@localhost/testdb", poolConfig); + +pool.query(sql).on("data", (row: Object[]): void => { +// nothing +}); + +pool.close((error: Error): void => { +}); + diff --git a/any-db/any-db.d.ts b/any-db/any-db.d.ts index fbdd4537e..04817623a 100644 --- a/any-db/any-db.d.ts +++ b/any-db/any-db.d.ts @@ -7,12 +7,12 @@ declare module "any-db" { import events = require("events"); - import stream = require("stream"); + import stream = require("stream"); export interface ConnectOpts { adapter: string; } - + export interface Adapter { name: string; /** @@ -20,10 +20,10 @@ declare module "any-db" { * If a continuation is given, it must be called, either with an error or the established connection. */ createConnection(opts: ConnectOpts, callback?: (error: Error, result: Connection) => void): Connection; - + /** - * Create a 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 + * Create a 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 */ createQuery(text: string, params?: any[], callback?: (error: Error, result: ResultSet) => void): Query; @@ -33,36 +33,62 @@ declare module "any-db" { * Other properties are driver specific */ export interface Field { - name: string; + name: string; } /** * 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 + * 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. */ export interface ResultSet { - fields: Field[]; - rows: Object[]; + /** + * Affected rows. Note e.g. for INSERT queries the rows property is not filled even + * though rowCount is non-zero. + */ rowCount: number; + /** + * Result rows + */ + rows: Object[]; + /** + * Result field descriptions + */ + fields: Field[]; + + /** + * Not supported by all drivers. + */ + fieldCount?: number; + /** + * Not supported by all drivers. + */ lastInsertId?: any; + /** + * Not supported by all drivers. + */ + affectedRows?: number; + /** + * Not supported by all drivers. + */ + changedRows?: number; } /** - * Query objects are returned by the Queryable.query method, available on connections, - * pools, and transactions. Queries are instances of Readable, and as such can be piped - * through transforms and support backpressure for more efficient memory-usage on very + * Query objects are returned by the Queryable.query method, available on connections, + * pools, and transactions. Queries are instances of Readable, and as such can be piped + * 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 and may have more methods, - * properties, and events than are described here. Consult the documentation for your + * Internally, Query instances are created by a database Adapter 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. * * Events: * * Error event - * The 'error' event is emitted at most once per query. Note that this event will be - * emitted for errors even if a callback was provided, the callback will + * The 'error' event is emitted at most once per query. Note that this event will be + * emitted for errors even if a callback was provided, the callback will * simply be subscribed to the 'error' event. * One argument is passed to event listeners: * error - the error object. @@ -89,25 +115,25 @@ declare module "any-db" { */ export interface Query extends stream.Readable { /** - * The SQL query as a string. If you are using MySQL this will contain + * The SQL query as a string. If you are using MySQL this will contain * interpolated values after the query has been enqueued by a connection. */ text: string; - + /** * The array of parameter values. */ values: any[]; - + /** - * The callback (if any) that was provided to Queryable.query. Note that - * Query objects must not use a closed over reference to their callback, - * as other any-db libraries may rely on modifying the callback property + * The callback (if any) that was provided to Queryable.query. Note that + * Query objects must not use a closed over reference to their callback, + * as other any-db libraries may rely on modifying the callback property * of a Query they did not create. */ callback: (error: Error, results: ResultSet) => void; } - + /** * Events: * The 'query' event is emitted immediately before a query is executed. One argument is passed to event handlers: @@ -118,30 +144,30 @@ declare module "any-db" { * The Adapter instance that will be used by this Queryable for creating Query instances and/or connections. */ adapter: Adapter; - + /** - * Execute a SQL statement using bound parameters (if they are provided) and return a Query object - * that is a Readable stream of the resulting rows. If a Continuation is provided the rows - * returned by the database will be aggregated into a [ResultSet][] which will be passed to the + * Execute a SQL statement using bound parameters (if they are provided) and return a Query object + * that is a Readable stream of the resulting rows. If a Continuation 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. - * The second form is not needed for normal use, but must be implemented by adapters to work correctly - * with ConnectionPool and Transaction. See Adapter.createQuery for more details. + * The second form is not needed for normal use, but must be implemented by adapters to work correctly + * with ConnectionPool and Transaction. See Adapter.createQuery for more details. */ query(text: string, params?: any[], callback?: (error: Error, results: ResultSet) => void): Query - + /** - * The second form is not needed for normal use, but must be implemented by adapters to work correctly - * with ConnectionPool and Transaction. See Adapter.createQuery for more details. + * The second form is not needed for normal use, but must be implemented by adapters to work correctly + * with ConnectionPool and Transaction. See Adapter.createQuery for more details. */ - query(query: Query): Query; + // query(query: Query): Query; } - + /** - * Connection objects are obtained using createConnection from Any-DB or ConnectionPool.acquire, + * Connection objects are obtained using createConnection from Any-DB or ConnectionPool.acquire, * both of which delegate to the createConnection implementation of the specified adapter. - * 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 + * 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. * * Events: @@ -155,64 +181,59 @@ declare module "any-db" { * * Close event * The 'close' event is emitted when the connection has been closed. - * No arguments are passed to event listeners. + * No arguments are passed to event listeners. */ - export interface Connection extends Queryable { + export interface Connection extends Queryable { /** - * Close the database connection. If a continuation is provided it + * Close the database connection. If a continuation is provided it * will be called after the connection has closed. */ end(callback?: (error: Error) => void): void; } - + export interface ConnectionStatic { new(): Connection; - + name: string; createConnection(): void; createPool(): void; } - + /** * ConnectionPool events * 'acquire' - emitted whenever pool.acquire is called * 'release' - emitted whenever pool.release is called * 'query', query - emitted immediately after .query is called on a * connection via pool.query. The argument is a Query object. - * 'close' - emitted when the connection pool has closed all of it + * 'close' - emitted when the connection pool has closed all of it * connections after a call to close(). */ - export interface ConnectionPool extends events.EventEmitter { + export interface ConnectionPool extends Queryable { /** - * The string name of the adapter used for this connection pool, e.g. 'sqlite3'. - */ - adapter: string; - - /** - * Implements Queryable.query by automatically acquiring a connection + * Implements Queryable.query by automatically acquiring a connection * and releasing it when the query completes. */ - query(text: string, params?: any[], callback?: (error: Error, results: ResultSet) => void): Query; - + query(text: string, params?: any[], callback?: (error: Error, results: ResultSet) => void): Query; + /** * Remove a connection from the pool. If you use this method you must * return the connection back to the pool using ConnectionPool.release */ acquire(callback: (error: Error, result: Connection) => void): void; - + /** - * Return a connection to the pool. This should only be called with connections + * Return a connection to the pool. This should only be called with connections * you've manually acquired. You must not continue to use the connection after releasing it. */ release(connection: Connection): void; - + /** - * Stop giving out new connections, and close all existing database connections as they + * Stop giving out new connections, and close all existing database connections as they * are returned to the pool. */ close(callback?: (error: Error) => void): void; } - + /** * A PoolConfig is generally a plain object with any of the following properties (they are all optional): */ @@ -222,8 +243,8 @@ declare module "any-db" { */ min?: number; /** - * max (default 10) The maximum number of connections to keep open in the pool. - * When this limit is reached further requests for connections will queue waiting + * max (default 10) The maximum number of connections to keep open in the pool. + * When this limit is reached further requests for connections will queue waiting * for an existing connection to be released back into the pool. */ max?: number; @@ -236,7 +257,7 @@ declare module "any-db" { */ reapInterval?: number; /** - * (default true) When this is true, the pool will reap connections that + * (default true) When this is true, the pool will reap connections that * have been idle for more than idleTimeout milliseconds. */ refreshIdle?: boolean; @@ -246,19 +267,19 @@ declare module "any-db" { */ onConnect?: (connection: Connection, ready: (error: Error, result: Connection) => void) => void; /** - * Called each time a connection is returned to the pool. Use this to restore a connection to - * it's original state (e.g. rollback transactions, set the database session vars). If reset + * Called each time a connection is returned to the pool. Use this to restore a connection to + * it's original state (e.g. rollback transactions, set the database session vars). If reset * fails to call the done continuation the connection will be lost in limbo. */ reset?: (connection: Connection, done: (error: Error) => void) => void; /** - * (default function (err) { return true }) - Called when an error is encountered - * by pool.query or emitted by an idle connection. If shouldDestroyConnection(error) + * (default function (err) { return true }) - Called when an error is encountered + * by pool.query or emitted by an idle connection. If shouldDestroyConnection(error) * is truthy the connection will be destroyed, otherwise it will be reset. */ shouldDestroyConnection?: (error: Error) => boolean; } - + /** * Create a database connection. * @param url String of the form adapter://user:password@host/database @@ -266,7 +287,7 @@ declare module "any-db" { * @returns Connection object. */ export function createConnection(url: string, callback?: (error: Error, connection: Connection) => void): Connection; - + /** * Create a database connection. * @param opts Object with adapter name and any properties that the given adapter requires @@ -274,8 +295,9 @@ declare module "any-db" { * @returns Connection object. */ export function createConnection(opts: ConnectOpts, callback?: (error: Error, connection: Connection) => void): Connection; - - + + export function createPool(url: string, config: PoolConfig): ConnectionPool; - + export function createPool(opts: ConnectOpts, config: PoolConfig): ConnectionPool; + }