Skip to content

Commit

Permalink
Merge 9f9ea1b into eb70ec1
Browse files Browse the repository at this point in the history
  • Loading branch information
digitalBush committed Jan 15, 2020
2 parents eb70ec1 + 9f9ea1b commit 69c9245
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 69 deletions.
19 changes: 14 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ const sql = skwell.connect( {
connectTimeout: 15000, //ms
requestTimeout: 15000, //ms
encrypt: false,
onBeginTransaction( tx ){
// Executes at the beginning of transaction
},
onEndTransaction( tx ){
// Executes at the end of transaction, right before commit
},
} );

```
Expand All @@ -39,7 +45,6 @@ sql.on( "error", err => {
// handle the error things
} )
```

The signature of everything except `bulkLoad` takes a query as the first argument. This query can be a string or a promise that resolves to a string. Skwell provides a `sql.file( "./relative.file.sql" )` method to load a file and cache the resulting text. If you are calling a stored procedure, use `sql.sproc( "name of stored procedure" )`.

Now, let's make some noise.
Expand Down Expand Up @@ -129,12 +134,16 @@ Sometimes you need to execute multiple queries in a transaction. Don't worry, we
``` js

// Passed as 2nd argument to `.transaction` below
const opts = {
isolationLevel: sql.read_uncommitted,
context: { userId: 123 } // This is set on the transaction
};

const result = await sql.transaction( async tx => {
const userId = 11;
const { userId } = tx.context; // context from opt.context above
const groupId = 89;

// any uses of `sql` within this transaction block will automatically happen on the transaction.

await tx.execute(
"INSERT INTO Users(id, name) values(@id, @name)",
{
Expand All @@ -148,7 +157,7 @@ const result = await sql.transaction( async tx => {
id: { val: groupId, type: sql.int },
userId: { val: userId, type: sql.int }
} );
}, sql.read_uncommitted );
}, opts );

// At this point, the transaction will be committed for you.
// If something would have broken, the transaction would have been rolled back.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "skwell",
"version": "5.4.0",
"version": "5.5.0-hooks.0",
"description": "SQL Server Client pooled and promised",
"main": "src/index.js",
"scripts": {
Expand Down
44 changes: 0 additions & 44 deletions spec/integration/extensibility.spec.js

This file was deleted.

5 changes: 0 additions & 5 deletions spec/integration/sql/extensibility-setup.sql

This file was deleted.

6 changes: 6 additions & 0 deletions spec/integration/sql/hooks-setup.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
DROP TABLE IF EXISTS dbo.HooksTest;
CREATE TABLE dbo.HooksTest(
[order] int identity,
[who] nvarchar(20),
[context] nvarchar(20)
);
52 changes: 52 additions & 0 deletions spec/integration/transaction-hooks.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const { config } = testHelpers;

const skwell = require( "src" );
describe( "Transaction Hooks - Integration", () => {
describe( "with begin and end transaction hooks in place", () => {
let sql;

function insert( tx, who ) {
return tx.execute( "insert into HooksTest(who, context) values(@who, @fromContext)", {
who: {
type: sql.nvarchar( 20 ),
val: who
},
fromContext: {
type: sql.nvarchar( 20 ),
val: tx.context.userName
}
} );
}

before( async () => {
sql = await skwell.connect( {
...config,
async onBeginTransaction( tx ) {
await insert( tx, "before" );
},
async onEndTransaction( tx ) {
await insert( tx, "after" );
}
} );

return sql.execute( sql.fromFile( "sql/hooks-setup.sql" ) );
} );

after( () => {
return sql.dispose();
} );

it( "should execute in proper order", async () => {
await sql.transaction( async tx => {
await insert( tx, "middle" );
}, { context: { userName: "Josh" } } );

const results = await sql.query( "select who,context from HooksTest order by [order]" );
results.should.deep.equal( [
{ who: "before", context: "Josh" },
{ who: "middle", context: "Josh" },
{ who: "after", context: "Josh" }
] );
} );
} );
} );
8 changes: 7 additions & 1 deletion spec/integration/transaction.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,17 @@ describe( "Transaction - Integration", () => {
} ).should.eventually.equal( "ReadCommitted" );
} );

it( "should set isolation level", async () => {
it( "should set isolation level as 2nd arg", async () => {
await sql.transaction( async tx => {
return tx.queryValue( isolationLevelQuery );
}, sql.read_uncommitted ).should.eventually.equal( "ReadUncommitted" );
} );

it( "should set isolation level as prop of 2nd arg", async () => {
await sql.transaction( async tx => {
return tx.queryValue( isolationLevelQuery );
}, { isolationLevel: sql.read_uncommitted } ).should.eventually.equal( "ReadUncommitted" );
} );
} );

describe( "rollback", () => {
Expand Down
39 changes: 29 additions & 10 deletions src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const Api = require( "./api" );
const Transaction = require( "./transaction" );
const poolFactory = require( "./poolFactory" );

const _pool = Symbol( "skwell:pool" );
const _state = Symbol( "skwell:client-state" );

class Client extends Api {

Expand All @@ -17,31 +17,50 @@ class Client extends Api {
this.emit( "error", e );
} );

this[ _pool ] = pool;
const {
onBeginTransaction = () => {},
onEndTransaction = () => {}
} = config;

this[ _state ] = {
pool,
onBeginTransaction,
onEndTransaction
};
}

async withConnection( action ) {
let conn;
let connection;
const { pool } = this[ _state ];
try {
conn = await this[ _pool ].acquire();
const result = await action( conn );
connection = await pool.acquire();
const result = await action( connection );
return result;
} finally {
if ( conn ) {
await this[ _pool ].release( conn );
if ( connection ) {
await pool.release( connection );
}
}
}

transaction( action, isolationLevel ) {
transaction( action, opts ) {
let isolationLevel, context;
if ( typeof opts === "object" ) {
( { isolationLevel, context } = opts );
} else if ( opts ) {
isolationLevel = opts;
}

if ( isolationLevel === undefined ) {
isolationLevel = ISOLATION_LEVEL.READ_COMMITTED;
}
return this.withConnection( conn => Transaction.run( conn, isolationLevel, action ) );
const { onBeginTransaction, onEndTransaction } = this[ _state ];

return this.withConnection( connection => Transaction.run( { connection, isolationLevel, action, context, onBeginTransaction, onEndTransaction } ) );
}

async dispose() {
const pool = this[ _pool ];
const { pool } = this[ _state ];
await pool.drain().then( () => {
pool.clear();
} );
Expand Down
9 changes: 6 additions & 3 deletions src/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ const _state = Symbol( "skwell:tx-state" );

class Transaction extends Api {

constructor( connection ) {
constructor( connection, context ) {
super();
this[ _state ] = { connection };
this.context = context;
}

static async run( connection, isolationLevel, action ) {
const tx = new Transaction( connection );
static async run( { connection, isolationLevel, action, context, onBeginTransaction, onEndTransaction } ) {
const tx = new Transaction( connection, context );
try {
await connection.beginTransaction( "" /* name */, isolationLevel );
await onBeginTransaction( tx );
const result = await action( tx );
await onEndTransaction( tx );
await connection.commitTransaction();
return result;
} catch ( err ) {
Expand Down

0 comments on commit 69c9245

Please sign in to comment.