diff --git a/.gitignore b/.gitignore
index 5a0bf29..645ffd9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,3 +29,4 @@ node_modules
 
 .idea/
 *.iml
+coverage/
\ No newline at end of file
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..0e75e56
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,36 @@
+# Logs
+logs
+*.log
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directory
+# Commenting this out is preferred by some people, see
+# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
+node_modules
+
+# Users Environment Variables
+.lock-wscript
+
+.idea/
+*.iml
+coverage/
+src/
+test/
+mocha.start.js
+webpack.config.js
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 75c08b4..545f551 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,18 +1,25 @@
 # Contributing Guide
 
-First, support is handled via the [Gitter Channel](https://gitter.im/js-data/js-data) and the [Mailing List](https://groups.io/org/groupsio/jsdata). Ask your questions there.
+First, support is handled via the [Slack Channel](http://slack.js-data.io) and
+the [Mailing List](https://groups.io/org/groupsio/jsdata). Ask your questions
+there.
 
-When submitting issues on GitHub, please include as much detail as possible to make debugging quick and easy.
+When submitting issues on GitHub, please include as much detail as possible to
+make debugging quick and easy.
 
-- good - Your versions of js-data, js-data-sql, etc., relevant console logs/error, code examples that revealed the issue
-- better - A [plnkr](http://plnkr.co/), [fiddle](http://jsfiddle.net/), or [bin](http://jsbin.com/?html,output) that demonstrates the issue
-- best - A Pull Request that fixes the issue, including test coverage for the issue and the fix
+- good - Your versions of js-data, js-data-sql, etc., relevant console
+logs/error, code examples that revealed the issue
+- better - A [plnkr](http://plnkr.co/), [fiddle](http://jsfiddle.net/), or
+[bin](http://jsbin.com/?html,output) that demonstrates the issue
+- best - A Pull Request that fixes the issue, including test coverage for the
+issue and the fix
 
 [Github Issues](https://github.com/js-data/js-data-sql/issues).
 
 #### Submitting Pull Requests
 
-1. Contribute to the issue/discussion that is the reason you'll be developing in the first place
+1. Contribute to the issue/discussion that is the reason you'll be developing in
+the first place
 1. Fork js-data-sql
 1. `git clone git@github.com:<you>/js-data-sql.git`
 1. `cd js-data-sql; npm install;`
@@ -20,7 +27,8 @@ When submitting issues on GitHub, please include as much detail as possible to m
 1. Run `npm test` (build and test)
   - You need io.js or Node 4.x that includes generator support without a flag
 1. Your code will be linted and checked for formatting, the tests will be run
-1. The `dist/` folder & files will be generated, do NOT commit `dist/*`! They will be committed when a release is cut.
+1. The `dist/` folder & files will be generated, do NOT commit `dist/*`! They
+will be committed when a release is cut.
 1. Submit your PR and we'll review!
 1. Thanks!
 
@@ -30,10 +38,13 @@ Here's how to make a release on the `master` branch:
 
 1. Bump `package.json` to the appropriate version.
 1. `npm test` must succeed.
-1. This time, the built `dist/js-data-sql.js` file _will_ be committed, so stage its changes.
+1. This time, the built `dist/js-data-sql.js` file _will_ be committed, so stage
+its changes.
 1. Mention the release version in the commit message, e.g. `Stable Version 1.2.3`
 1. Push to master.
 1. Create a git tag. Name it the version of the release, e.g. `1.2.3`
-  - Easiest way is to just create a GitHub Release, which will create the tag for you. Name the Release and the git tag the same thing.
-1. `git fetch origin` if you tagged it via GitHub Release, so you can get the tag on your local machine.
+  - Easiest way is to just create a GitHub Release, which will create the tag
+for you. Name the Release and the git tag the same thing.
+1. `git fetch origin` if you tagged it via GitHub Release, so you can get the
+tag on your local machine.
 1. `npm publish .` (Make sure you got the version bumped correctly!)
\ No newline at end of file
diff --git a/README.md b/README.md
index c921b61..b32ae0f 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 <img src="https://raw.githubusercontent.com/js-data/js-data/master/js-data.png" alt="js-data logo" title="js-data" align="right" width="64" height="64" />
 
-## js-data-sql [![npm version](https://img.shields.io/npm/v/js-data-sql.svg?style=flat-square)](https://www.npmjs.org/package/js-data-sql) [![Circle CI](https://img.shields.io/circleci/project/js-data/js-data-sql/master.svg?style=flat-square)](https://circleci.com/gh/js-data/js-data-sql/tree/master) [![npm downloads](https://img.shields.io/npm/dm/js-data-sql.svg?style=flat-square)](https://www.npmjs.org/package/js-data-sql)
+## js-data-sql [![Slack Status][sl_b]][sl_l] [![npm version][npm_b]][npm_l] [![Circle CI][circle_b]][circle_l] [![npm downloads][dn_b]][dn_l] [![Coverage Status][cov_b]][cov_l] [![Codacy][cod_b]][cod_l]
 
 Postgres/MySQL/MariaDB/SQLite3 adapter for [js-data](http://www.js-data.io/).
 
@@ -8,7 +8,7 @@ Postgres/MySQL/MariaDB/SQLite3 adapter for [js-data](http://www.js-data.io/).
 [DSSqlAdapter](http://www.js-data.io/docs/dssqladapter)
 
 ### Quick Start
-`npm install --save js-data js-data-sql`.
+`npm install --save knex js-data js-data-sql`.
 
 You also need to install the driver for the database you want to connect to.
 
@@ -39,7 +39,7 @@ Read about using [JSData on the Server](http://www.js-data.io/docs/jsdata-on-the
 [CHANGELOG.md](https://github.com/js-data/js-data-sql/blob/master/CHANGELOG.md)
 
 ### Community
-- [Gitter Channel](https://gitter.im/js-data/js-data) - Better than IRC!
+- [Slack Channel](http://slack.js-data.io) - Better than IRC!
 - [Announcements](http://www.js-data.io/blog)
 - [Mailing List](https://groups.io/org/groupsio/jsdata) - Ask your questions!
 - [Issues](https://github.com/js-data/js-data-sql/issues) - Found a bug? Feature request? Submit an issue!
@@ -48,19 +48,26 @@ Read about using [JSData on the Server](http://www.js-data.io/docs/jsdata-on-the
 
 ### Contributing
 
-First, support is handled via the [Gitter Channel](https://gitter.im/js-data/js-data) and the [Mailing List](https://groups.io/org/groupsio/jsdata). Ask your questions there.
+First, support is handled via the [Slack Channel](http://slack.js-data.io) and
+the [Mailing List](https://groups.io/org/groupsio/jsdata). Ask your questions
+there.
 
-When submitting issues on GitHub, please include as much detail as possible to make debugging quick and easy.
+When submitting issues on GitHub, please include as much detail as possible to
+make debugging quick and easy.
 
-- good - Your versions of js-data, js-data-sql, etc., relevant console logs/error, code examples that revealed the issue
-- better - A [plnkr](http://plnkr.co/), [fiddle](http://jsfiddle.net/), or [bin](http://jsbin.com/?html,output) that demonstrates the issue
-- best - A Pull Request that fixes the issue, including test coverage for the issue and the fix
+- good - Your versions of js-data, js-data-sql, etc., relevant console
+logs/error, code examples that revealed the issue
+- better - A [plnkr](http://plnkr.co/), [fiddle](http://jsfiddle.net/), or
+[bin](http://jsbin.com/?html,output) that demonstrates the issue
+- best - A Pull Request that fixes the issue, including test coverage for the
+issue and the fix
 
 [Github Issues](https://github.com/js-data/js-data-sql/issues).
 
 #### Submitting Pull Requests
 
-1. Contribute to the issue/discussion that is the reason you'll be developing in the first place
+1. Contribute to the issue/discussion that is the reason you'll be developing in
+the first place
 1. Fork js-data-sql
 1. `git clone git@github.com:<you>/js-data-sql.git`
 1. `cd js-data-sql; npm install;`
@@ -68,7 +75,8 @@ When submitting issues on GitHub, please include as much detail as possible to m
 1. Run `npm test` (build and test)
   - You need io.js or Node 4.x that includes generator support without a flag
 1. Your code will be linted and checked for formatting, the tests will be run
-1. The `dist/` folder & files will be generated, do NOT commit `dist/*`! They will be committed when a release is cut.
+1. The `dist/` folder & files will be generated, do NOT commit `dist/*`! They
+will be committed when a release is cut.
 1. Submit your PR and we'll review!
 1. Thanks!
 
@@ -109,3 +117,16 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.
+
+[sl_b]: http://slack.js-data.io/badge.svg
+[sl_l]: http://slack.js-data.io
+[npm_b]: https://img.shields.io/npm/v/js-data-sql.svg?style=flat
+[npm_l]: https://www.npmjs.org/package/js-data-sql
+[circle_b]: https://img.shields.io/circleci/project/js-data/js-data-sql/master.svg?style=flat
+[circle_l]: https://circleci.com/gh/js-data/js-data-sql/tree/master
+[dn_b]: https://img.shields.io/npm/dm/js-data-sql.svg?style=flat
+[dn_l]: https://www.npmjs.org/package/js-data-sql
+[cov_b]: https://img.shields.io/coveralls/js-data/js-data-sql/master.svg?style=flat
+[cov_l]: https://coveralls.io/github/js-data/js-data-sql?branch=master
+[cod_b]: https://img.shields.io/codacy/307c2e9399394fdaa5354cda7329516d.svg
+[cod_l]: https://www.codacy.com/app/jasondobry/js-data-sql/dashboard
diff --git a/circle.yml b/circle.yml
index 8bcf7f9..c2da8a5 100644
--- a/circle.yml
+++ b/circle.yml
@@ -5,3 +5,6 @@ machine:
 database:
   override:
     - mysql -u ubuntu circle_test < test/setup.sql
+test:
+  override:
+    - npm run ci
diff --git a/dist/js-data-sql.js b/dist/js-data-sql.js
index 560d2fa..f2d846e 100644
--- a/dist/js-data-sql.js
+++ b/dist/js-data-sql.js
@@ -45,20 +45,21 @@ module.exports =
 /* 0 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })();
+	'use strict';
 
-	var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+	var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; })();
 
-	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+	var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
 
 	function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
 
 	var knex = __webpack_require__(1);
 	var JSData = __webpack_require__(2);
-	var map = __webpack_require__(3);
-	var underscore = __webpack_require__(4);
-	var unique = __webpack_require__(5);
-	var toString = __webpack_require__(6);
+	var underscore = __webpack_require__(3);
+	var unique = __webpack_require__(4);
+	var toString = __webpack_require__(5);
 	var DSUtils = JSData.DSUtils;
 
 	var reserved = ['orderBy', 'sort', 'limit', 'offset', 'skip', 'where'];
@@ -67,154 +68,6 @@ module.exports =
 	  return resourceConfig.table || underscore(resourceConfig.name);
 	}
 
-	function filterQuery(resourceConfig, params) {
-	  var table = getTable(resourceConfig);
-	  var query = this.query.select(table + '.*').from(table);
-	  params = params || {};
-	  params.where = params.where || {};
-	  params.orderBy = params.orderBy || params.sort;
-	  params.skip = params.skip || params.offset;
-
-	  var joinedTables = [];
-
-	  DSUtils.forEach(DSUtils.keys(params), function (k) {
-	    var v = params[k];
-	    if (!DSUtils.contains(reserved, k)) {
-	      if (DSUtils.isObject(v)) {
-	        params.where[k] = v;
-	      } else {
-	        params.where[k] = {
-	          '==': v
-	        };
-	      }
-	      delete params[k];
-	    }
-	  });
-
-	  if (!DSUtils.isEmpty(params.where)) {
-	    DSUtils.forOwn(params.where, function (criteria, field) {
-	      if (!DSUtils.isObject(criteria)) {
-	        params.where[field] = {
-	          '==': criteria
-	        };
-	      }
-
-	      DSUtils.forOwn(criteria, function (v, op) {
-	        if (DSUtils.contains(field, '.')) {
-	          (function () {
-	            var parts = field.split('.');
-	            var localResourceConfig = resourceConfig;
-
-	            var relationPath = [];
-
-	            var _loop = function () {
-	              var relationName = parts.shift();
-	              var relationResourceConfig = resourceConfig.getResource(relationName);
-	              relationPath.push(relationName);
-
-	              if (!joinedTables.some(function (t) {
-	                return t === relationPath.join('.');
-	              })) {
-	                var _localResourceConfig$relationList$filter = localResourceConfig.relationList.filter(function (r) {
-	                  return r.relation === relationName;
-	                });
-
-	                var _localResourceConfig$relationList$filter2 = _slicedToArray(_localResourceConfig$relationList$filter, 1);
-
-	                var relation = _localResourceConfig$relationList$filter2[0];
-
-	                var _table = getTable(localResourceConfig);
-	                var localId = _table + '.' + relation.localKey;
-
-	                var relationTable = getTable(relationResourceConfig);
-	                var foreignId = relationTable + '.' + relationResourceConfig.idAttribute;
-
-	                query = query.join(relationTable, localId, foreignId);
-	                joinedTables.push(relationPath.join('.'));
-	              }
-	              localResourceConfig = relationResourceConfig;
-	            };
-
-	            while (parts.length >= 2) {
-	              _loop();
-	            }
-
-	            field = getTable(localResourceConfig) + '.' + parts[0];
-	          })();
-	        }
-
-	        if (op === '==' || op === '===') {
-	          query = query.where(field, v);
-	        } else if (op === '!=' || op === '!==') {
-	          query = query.where(field, '!=', v);
-	        } else if (op === '>') {
-	          query = query.where(field, '>', v);
-	        } else if (op === '>=') {
-	          query = query.where(field, '>=', v);
-	        } else if (op === '<') {
-	          query = query.where(field, '<', v);
-	        } else if (op === '<=') {
-	          query = query.where(field, '<=', v);
-	          // } else if (op === 'isectEmpty') {
-	          //  subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)
-	          // } else if (op === 'isectNotEmpty') {
-	          //  subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)
-	        } else if (op === 'in') {
-	            query = query.where(field, 'in', v);
-	          } else if (op === 'notIn') {
-	            query = query.whereNotIn(field, v);
-	          } else if (op === 'like') {
-	            query = query.where(field, 'like', v);
-	          } else if (op === '|==' || op === '|===') {
-	            query = query.orWhere(field, v);
-	          } else if (op === '|!=' || op === '|!==') {
-	            query = query.orWhere(field, '!=', v);
-	          } else if (op === '|>') {
-	            query = query.orWhere(field, '>', v);
-	          } else if (op === '|>=') {
-	            query = query.orWhere(field, '>=', v);
-	          } else if (op === '|<') {
-	            query = query.orWhere(field, '<', v);
-	          } else if (op === '|<=') {
-	            query = query.orWhere(field, '<=', v);
-	            // } else if (op === '|isectEmpty') {
-	            //  subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)
-	            // } else if (op === '|isectNotEmpty') {
-	            //  subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)
-	          } else if (op === '|in') {
-	              query = query.orWhere(field, 'in', v);
-	            } else if (op === '|notIn') {
-	              query = query.orWhereNotIn(field, v);
-	            } else {
-	              throw new Error('Operator not found');
-	            }
-	      });
-	    });
-	  }
-
-	  if (params.orderBy) {
-	    if (DSUtils.isString(params.orderBy)) {
-	      params.orderBy = [[params.orderBy, 'asc']];
-	    }
-	    for (var i = 0; i < params.orderBy.length; i++) {
-	      if (DSUtils.isString(params.orderBy[i])) {
-	        params.orderBy[i] = [params.orderBy[i], 'asc'];
-	      }
-	      query = DSUtils.upperCase(params.orderBy[i][1]) === 'DESC' ? query.orderBy(params.orderBy[i][0], 'desc') : query.orderBy(params.orderBy[i][0], 'asc');
-	    }
-	  }
-
-	  if (params.skip) {
-	    query = query.offset(+params.offset);
-	  }
-
-	  if (params.limit) {
-	    query = query.limit(+params.limit);
-	  }
-
-	  return query;
-	}
-
 	function loadWithRelations(items, resourceConfig, options) {
 	  var _this = this;
 
@@ -226,9 +79,9 @@ module.exports =
 	    var relationDef = resourceConfig.getResource(relationName);
 
 	    var containedName = null;
-	    if (DSUtils.contains(options['with'], relationName)) {
+	    if (DSUtils.contains(options.with, relationName)) {
 	      containedName = relationName;
-	    } else if (DSUtils.contains(options['with'], def.localField)) {
+	    } else if (DSUtils.contains(options.with, def.localField)) {
 	      containedName = def.localField;
 	    } else {
 	      return;
@@ -237,7 +90,7 @@ module.exports =
 	    var __options = DSUtils.deepMixIn({}, options.orig ? options.orig() : options);
 
 	    // Filter to only properties under current relation
-	    __options['with'] = options['with'].filter(function (relation) {
+	    __options.with = options.with.filter(function (relation) {
 	      return relation !== containedName && relation.indexOf(containedName) === 0 && relation.length >= containedName.length && relation[containedName.length] === '.';
 	    }).map(function (relation) {
 	      return relation.substr(containedName.length + 1);
@@ -250,7 +103,7 @@ module.exports =
 	      if (instance) {
 	        foreignKeyFilter = { '==': instance[resourceConfig.idAttribute] };
 	      } else {
-	        foreignKeyFilter = { 'in': map(items, function (item) {
+	        foreignKeyFilter = { 'in': items.map(function (item) {
 	            return item[resourceConfig.idAttribute];
 	          }) };
 	      }
@@ -326,7 +179,7 @@ module.exports =
 	      } else {
 	        task = _this.findAll(resourceConfig.getResource(relationName), {
 	          where: _defineProperty({}, relationDef.idAttribute, {
-	            'in': DSUtils.filter(map(items, function (item) {
+	            'in': DSUtils.filter(items.map(function (item) {
 	              return DSUtils.get(item, def.localKey);
 	            }), function (x) {
 	              return x;
@@ -373,8 +226,9 @@ module.exports =
 
 	      var instance = undefined;
 	      options = options || {};
-	      options['with'] = options['with'] || [];
-	      return this.query.select('*').from(getTable(resourceConfig)).where(resourceConfig.idAttribute, toString(id)).then(function (rows) {
+	      options.with = options.with || [];
+	      var query = options && options.transaction || this.query;
+	      return query.select('*').from(getTable(resourceConfig)).where(resourceConfig.idAttribute, toString(id)).then(function (rows) {
 	        if (!rows.length) {
 	          return DSUtils.Promise.reject(new Error('Not Found!'));
 	        } else {
@@ -392,8 +246,8 @@ module.exports =
 
 	      var items = null;
 	      options = options || {};
-	      options['with'] = options['with'] || [];
-	      return filterQuery.call(this, resourceConfig, params, options).then(function (_items) {
+	      options.with = options.with || [];
+	      return this.filterQuery(resourceConfig, params, options).then(function (_items) {
 	        items = _items;
 	        return loadWithRelations.call(_this3, _items, resourceConfig, options);
 	      }).then(function () {
@@ -406,7 +260,8 @@ module.exports =
 	      var _this4 = this;
 
 	      attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || []));
-	      return this.query(getTable(resourceConfig)).insert(attrs, resourceConfig.idAttribute).then(function (ids) {
+	      var query = options && options.transaction || this.query;
+	      return query(getTable(resourceConfig)).insert(attrs, resourceConfig.idAttribute).then(function (ids) {
 	        if (attrs[resourceConfig.idAttribute]) {
 	          return _this4.find(resourceConfig, attrs[resourceConfig.idAttribute], options);
 	        } else if (ids.length) {
@@ -422,7 +277,8 @@ module.exports =
 	      var _this5 = this;
 
 	      attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || []));
-	      return this.query(getTable(resourceConfig)).where(resourceConfig.idAttribute, toString(id)).update(attrs).then(function () {
+	      var query = options && options.transaction || this.query;
+	      return query(getTable(resourceConfig)).where(resourceConfig.idAttribute, toString(id)).update(attrs).then(function () {
 	        return _this5.find(resourceConfig, id, options);
 	      });
 	    }
@@ -432,34 +288,199 @@ module.exports =
 	      var _this6 = this;
 
 	      attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || []));
-	      return filterQuery.call(this, resourceConfig, params, options).then(function (items) {
-	        return map(items, function (item) {
+	      return this.filterQuery(resourceConfig, params, options).then(function (items) {
+	        return items.map(function (item) {
 	          return item[resourceConfig.idAttribute];
 	        });
 	      }).then(function (ids) {
-	        return filterQuery.call(_this6, resourceConfig, params, options).update(attrs).then(function () {
+	        return _this6.filterQuery(resourceConfig, params, options).update(attrs).then(function () {
 	          var _params = { where: {} };
 	          _params.where[resourceConfig.idAttribute] = {
 	            'in': ids
 	          };
-	          return filterQuery.call(_this6, resourceConfig, _params, options);
+	          return _this6.filterQuery(resourceConfig, _params, options);
 	        });
 	      });
 	    }
 	  }, {
 	    key: 'destroy',
-	    value: function destroy(resourceConfig, id) {
-	      return this.query(getTable(resourceConfig)).where(resourceConfig.idAttribute, toString(id)).del().then(function () {
+	    value: function destroy(resourceConfig, id, options) {
+	      var query = options && options.transaction || this.query;
+	      return query(getTable(resourceConfig)).where(resourceConfig.idAttribute, toString(id)).del().then(function () {
 	        return undefined;
 	      });
 	    }
 	  }, {
 	    key: 'destroyAll',
 	    value: function destroyAll(resourceConfig, params, options) {
-	      return filterQuery.call(this, resourceConfig, params, options).del().then(function () {
+	      return this.filterQuery(resourceConfig, params, options).del().then(function () {
 	        return undefined;
 	      });
 	    }
+	  }, {
+	    key: 'filterQuery',
+	    value: function filterQuery(resourceConfig, params, options) {
+	      var table = getTable(resourceConfig);
+	      var query = undefined;
+
+	      if (params instanceof Object.getPrototypeOf(this.query.client).QueryBuilder) {
+	        query = params;
+	        params = {};
+	      } else if (options && options.query) {
+	        query = options.query || this.query;
+	      } else {
+	        query = options && options.transaction || this.query;
+	        query = query.select(table + '.*').from(table);
+	      }
+
+	      params = params || {};
+	      params.where = params.where || {};
+	      params.orderBy = params.orderBy || params.sort;
+	      params.skip = params.skip || params.offset;
+
+	      var joinedTables = [];
+
+	      DSUtils.forEach(DSUtils.keys(params), function (k) {
+	        var v = params[k];
+	        if (!DSUtils.contains(reserved, k)) {
+	          if (DSUtils.isObject(v)) {
+	            params.where[k] = v;
+	          } else {
+	            params.where[k] = {
+	              '==': v
+	            };
+	          }
+	          delete params[k];
+	        }
+	      });
+
+	      if (!DSUtils.isEmpty(params.where)) {
+	        DSUtils.forOwn(params.where, function (criteria, field) {
+	          if (!DSUtils.isObject(criteria)) {
+	            params.where[field] = {
+	              '==': criteria
+	            };
+	          }
+
+	          DSUtils.forOwn(criteria, function (v, op) {
+	            if (DSUtils.contains(field, '.')) {
+	              (function () {
+	                var parts = field.split('.');
+	                var localResourceConfig = resourceConfig;
+
+	                var relationPath = [];
+
+	                var _loop = function _loop() {
+	                  var relationName = parts.shift();
+	                  var relationResourceConfig = resourceConfig.getResource(relationName);
+	                  relationPath.push(relationName);
+
+	                  if (!joinedTables.some(function (t) {
+	                    return t === relationPath.join('.');
+	                  })) {
+	                    var _localResourceConfig$ = localResourceConfig.relationList.filter(function (r) {
+	                      return r.relation === relationName;
+	                    });
+
+	                    var _localResourceConfig$2 = _slicedToArray(_localResourceConfig$, 1);
+
+	                    var relation = _localResourceConfig$2[0];
+
+	                    if (relation) {
+	                      var _table = getTable(localResourceConfig);
+	                      var localId = _table + '.' + relation.localKey;
+
+	                      var relationTable = getTable(relationResourceConfig);
+	                      var foreignId = relationTable + '.' + relationResourceConfig.idAttribute;
+
+	                      query = query.join(relationTable, localId, foreignId);
+	                      joinedTables.push(relationPath.join('.'));
+	                    } else {
+	                      // local column
+	                    }
+	                  }
+	                  localResourceConfig = relationResourceConfig;
+	                };
+
+	                while (parts.length >= 2) {
+	                  _loop();
+	                }
+
+	                field = getTable(localResourceConfig) + '.' + parts[0];
+	              })();
+	            }
+
+	            if (op === '==' || op === '===') {
+	              query = query.where(field, v);
+	            } else if (op === '!=' || op === '!==') {
+	              query = query.where(field, '!=', v);
+	            } else if (op === '>') {
+	              query = query.where(field, '>', v);
+	            } else if (op === '>=') {
+	              query = query.where(field, '>=', v);
+	            } else if (op === '<') {
+	              query = query.where(field, '<', v);
+	            } else if (op === '<=') {
+	              query = query.where(field, '<=', v);
+	              // } else if (op === 'isectEmpty') {
+	              //  subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)
+	              // } else if (op === 'isectNotEmpty') {
+	              //  subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)
+	            } else if (op === 'in') {
+	                query = query.where(field, 'in', v);
+	              } else if (op === 'notIn') {
+	                query = query.whereNotIn(field, v);
+	              } else if (op === 'like') {
+	                query = query.where(field, 'like', v);
+	              } else if (op === '|==' || op === '|===') {
+	                query = query.orWhere(field, v);
+	              } else if (op === '|!=' || op === '|!==') {
+	                query = query.orWhere(field, '!=', v);
+	              } else if (op === '|>') {
+	                query = query.orWhere(field, '>', v);
+	              } else if (op === '|>=') {
+	                query = query.orWhere(field, '>=', v);
+	              } else if (op === '|<') {
+	                query = query.orWhere(field, '<', v);
+	              } else if (op === '|<=') {
+	                query = query.orWhere(field, '<=', v);
+	                // } else if (op === '|isectEmpty') {
+	                //  subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)
+	                // } else if (op === '|isectNotEmpty') {
+	                //  subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)
+	              } else if (op === '|in') {
+	                  query = query.orWhere(field, 'in', v);
+	                } else if (op === '|notIn') {
+	                  query = query.orWhereNotIn(field, v);
+	                } else {
+	                  throw new Error('Operator not found');
+	                }
+	          });
+	        });
+	      }
+
+	      if (params.orderBy) {
+	        if (DSUtils.isString(params.orderBy)) {
+	          params.orderBy = [[params.orderBy, 'asc']];
+	        }
+	        for (var i = 0; i < params.orderBy.length; i++) {
+	          if (DSUtils.isString(params.orderBy[i])) {
+	            params.orderBy[i] = [params.orderBy[i], 'asc'];
+	          }
+	          query = DSUtils.upperCase(params.orderBy[i][1]) === 'DESC' ? query.orderBy(params.orderBy[i][0], 'desc') : query.orderBy(params.orderBy[i][0], 'asc');
+	        }
+	      }
+
+	      if (params.skip) {
+	        query = query.offset(+params.offset);
+	      }
+
+	      if (params.limit) {
+	        query = query.limit(+params.limit);
+	      }
+
+	      return query;
+	    }
 	  }]);
 
 	  return DSSqlAdapter;
@@ -481,24 +502,18 @@ module.exports =
 
 /***/ },
 /* 3 */
-/***/ function(module, exports) {
-
-	module.exports = require("mout/array/map");
-
-/***/ },
-/* 4 */
 /***/ function(module, exports) {
 
 	module.exports = require("mout/string/underscore");
 
 /***/ },
-/* 5 */
+/* 4 */
 /***/ function(module, exports) {
 
 	module.exports = require("mout/array/unique");
 
 /***/ },
-/* 6 */
+/* 5 */
 /***/ function(module, exports) {
 
 	module.exports = require("mout/lang/toString");
diff --git a/mocha.start.js b/mocha.start.js
index ee83284..9c42f3f 100644
--- a/mocha.start.js
+++ b/mocha.start.js
@@ -1,44 +1,21 @@
 /*global assert:true */
 'use strict';
 
-var assert = require('chai').assert;
-assert.equalObjects = function (a, b, m) {
-  assert.deepEqual(JSON.parse(JSON.stringify(a)), JSON.parse(JSON.stringify(b)), m || 'Objects should be equal!');
-};
+var JSData = require('js-data');
+var TestRunner = require('js-data-adapter-tests');
+
 var mocha = require('mocha');
 var coMocha = require('co-mocha');
+
 coMocha(mocha);
-var JSData = require('js-data');
 JSData.DSUtils.Promise = require('bluebird');
-var DSSqlAdapter = require('./');
 
-var adapter, store, DSUtils, DSErrors, Profile, User, Post, Comment;
+var DSSqlAdapter = require('./');
 
 var globals = module.exports = {
-  fail: function (msg) {
-    assert.equal('should not reach this!: ' + msg, 'failure');
-  },
-  TYPES_EXCEPT_STRING: [123, 123.123, null, undefined, {}, [], true, false, function () {
-  }],
-  TYPES_EXCEPT_STRING_OR_ARRAY: [123, 123.123, null, undefined, {}, true, false, function () {
-  }],
-  TYPES_EXCEPT_STRING_OR_NUMBER: [null, undefined, {}, [], true, false, function () {
-  }],
-  TYPES_EXCEPT_STRING_OR_OBJECT: [123, 123.123, null, undefined, [], true, false, function () {
-  }],
-  TYPES_EXCEPT_STRING_OR_NUMBER_OBJECT: [null, undefined, [], true, false, function () {
-  }],
-  TYPES_EXCEPT_STRING_OR_ARRAY_OR_NUMBER: [null, undefined, {}, true, false, function () {
-  }],
-  TYPES_EXCEPT_NUMBER: ['string', null, undefined, {}, [], true, false, function () {
-  }],
-  TYPES_EXCEPT_OBJECT: ['string', 123, 123.123, null, undefined, true, false, function () {
-  }],
-  TYPES_EXCEPT_BOOLEAN: ['string', 123, 123.123, null, undefined, {}, [], function () {
-  }],
-  TYPES_EXCEPT_FUNCTION: ['string', 123, 123.123, null, undefined, {}, [], true, false],
-  assert: assert,
-  adapter: undefined
+  TestRunner: TestRunner,
+  assert: TestRunner.assert,
+  co: require('co')
 };
 
 var test = new mocha();
@@ -51,90 +28,29 @@ for (var key in globals) {
 }
 test.globals(testGlobals);
 
-beforeEach(function () {
-  store = new JSData.DS({
-    log: false
-  });
-  adapter = new DSSqlAdapter({
-    client: 'mysql',
-    connection: {
-      user: process.env.C9_USER || 'ubuntu',
-      database: process.env.C9_USER ? 'c9' : 'circle_test'
-      //user: 'root',
-      //database: 'test'
-    }
-  });
-  DSUtils = JSData.DSUtils;
-  DSErrors = JSData.DSErrors;
-  globals.Profile = global.Profile = Profile = store.defineResource({
-    name: 'profile'
-  });
-  globals.User = global.User = User = store.defineResource({
-    name: 'user',
-    relations: {
-      hasMany: {
-        post: {
-          localField: 'posts',
-          foreignKey: 'post'
-        }
-      },
-      hasOne: {
-        profile: {
-          localField: 'profile',
-          localKey: 'profileId'
-        }
-      }
-    }
-  });
-  globals.Post = global.Post = Post = store.defineResource({
-    name: 'post',
-    relations: {
-      belongsTo: {
-        user: {
-          localField: 'user',
-          localKey: 'userId'
-        }
-      },
-      hasMany: {
-        comment: {
-          localField: 'comments',
-          foreignKey: 'postId'
-        }
-      }
-    }
-  });
-  globals.Comment = global.Comment = Comment = store.defineResource({
-    name: 'comment',
-    relations: {
-      belongsTo: {
-        post: {
-          localField: 'post',
-          localKey: 'postId'
-        },
-        user: {
-          localField: 'user',
-          localKey: 'userId'
-        }
-      }
-    }
-  });
-
-  globals.adapter = adapter;
-  global.adapter = globals.adapter;
-
-  globals.DSUtils = DSUtils;
-  global.DSUtils = globals.DSUtils;
+var config = {
+  client: 'mysql',
+  connection: {
+    host: process.env.DB_HOST || 'localhost',
+    user: process.env.DB_USER || process.env.C9_USER || 'ubuntu',
+    database: process.env.DB_NAME || (process.env.C9_USER ? 'c9' : 'circle_test')
+  }
+};
 
-  globals.DSErrors = DSErrors;
-  global.DSErrors = globals.DSErrors;
+TestRunner.init({
+  DS: JSData.DS,
+  Adapter: DSSqlAdapter,
+  adapterConfig: config
 });
 
-afterEach(function* () {
-  globals.adapter = null;
-  global.adapter = null;
-
-  yield adapter.destroyAll(Comment);
-  yield adapter.destroyAll(Post);
-  yield adapter.destroyAll(User);
-  yield adapter.destroyAll(Profile);
+beforeEach(function () {
+  globals.DSUtils = global.DSUtils = this.$$DSUtils;
+  globals.DSErrors = global.DSErrors = this.$$DSErrors;
+  globals.adapter = global.adapter = this.$$adapter;
+  globals.store = global.store = this.$$store;
+  globals.User = global.User = this.$$User;
+  globals.Profile = global.Profile = this.$$Profile;
+  globals.Address = global.Address = this.$$Address;
+  globals.Post = global.Post = this.$$Post;
+  globals.Comment = global.Comment = this.$$Comment;
 });
diff --git a/package.json b/package.json
index f21779d..3bdbb22 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "js-data-sql",
   "description": "Postgres/MySQL/MariaDB/SQLite3 adapter for js-data.",
-  "version": "0.11.2",
+  "version": "0.11.5",
   "homepage": "http://www.js-data.io/docs/dssqladapter",
   "repository": {
     "type": "git",
@@ -25,31 +25,39 @@
     "mariadb",
     "sqlite"
   ],
-  "devDependencies": {
-    "babel-core": "5.8.25",
-    "babel-eslint": "4.1.3",
-    "babel-loader": "5.3.2",
-    "bluebird": "2.10.2",
-    "chai": "3.3.0",
-    "co-mocha": "1.1.2",
-    "mocha": "2.3.3",
-    "standard": "5.3.1",
-    "webpack": "1.12.2"
-  },
   "scripts": {
-    "lint": "node_modules/standard/bin/cmd.js src/index.js",
-    "build": "node_modules/webpack/bin/webpack.js --config webpack.config.js --progress --colors",
-    "mocha": "mocha test/*.spec.js --timeout 20000 --reporter spec",
-    "test": "npm run lint && npm run build && npm run mocha"
+    "lint": "standard src/index.js",
+    "build": "webpack --config webpack.config.js --progress --colors",
+    "mocha": "mocha --timeout 20000 --reporter dot mocha.start.js test/*.spec.js",
+    "cover": "istanbul cover --hook-run-in-context node_modules/mocha/bin/_mocha -- --timeout 20000 --reporter dot mocha.start.js test/*.spec.js",
+    "test": "npm run lint && npm run build && npm run cover",
+    "ci": "npm run test && cat coverage/lcov.info | coveralls || true && cat ./coverage/lcov.info | codacy-coverage || true"
   },
   "standard": {
     "parser": "babel-eslint"
   },
   "dependencies": {
-    "mout": "0.11.0"
+    "mout": "0.11.1"
   },
   "peerDependencies": {
-    "js-data": ">=2.0.0",
+    "js-data": "~2.x",
     "knex": ">=0.7.4"
+  },
+  "devDependencies": {
+    "babel-core": "6.1.4",
+    "babel-eslint": "4.1.5",
+    "babel-loader": "6.1.0",
+    "babel-preset-es2015": "6.1.2",
+    "bluebird": "2.10.2",
+    "chai": "3.4.1",
+    "co": "4.6.0",
+    "co-mocha": "1.1.2",
+    "codacy-coverage": "1.1.3",
+    "coveralls": "2.11.4",
+    "istanbul": "0.4.0",
+    "js-data-adapter-tests": "~1.x",
+    "mocha": "2.3.3",
+    "standard": "5.3.1",
+    "webpack": "1.12.2"
   }
 }
diff --git a/src/index.js b/src/index.js
index 8131117..efff30b 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,6 +1,5 @@
 let knex = require('knex')
 let JSData = require('js-data')
-let map = require('mout/array/map')
 let underscore = require('mout/string/underscore')
 let unique = require('mout/array/unique')
 let toString = require('mout/lang/toString')
@@ -19,138 +18,48 @@ function getTable (resourceConfig) {
   return resourceConfig.table || underscore(resourceConfig.name)
 }
 
-function filterQuery (resourceConfig, params) {
-  let table = getTable(resourceConfig)
-  let query = this.query.select(`${table}.*`).from(table)
-  params = params || {}
-  params.where = params.where || {}
-  params.orderBy = params.orderBy || params.sort
-  params.skip = params.skip || params.offset
-
-  let joinedTables = []
-
-  DSUtils.forEach(DSUtils.keys(params), k => {
-    let v = params[k]
-    if (!DSUtils.contains(reserved, k)) {
-      if (DSUtils.isObject(v)) {
-        params.where[k] = v
-      } else {
-        params.where[k] = {
-          '==': v
-        }
-      }
-      delete params[k]
-    }
-  })
-
-  if (!DSUtils.isEmpty(params.where)) {
-    DSUtils.forOwn(params.where, (criteria, field) => {
-      if (!DSUtils.isObject(criteria)) {
-        params.where[field] = {
-          '==': criteria
-        }
-      }
-
-      DSUtils.forOwn(criteria, (v, op) => {
-        if (DSUtils.contains(field, '.')) {
-          let parts = field.split('.')
-          let localResourceConfig = resourceConfig
-
-          let relationPath = []
-          while (parts.length >= 2) {
-            let relationName = parts.shift()
-            let relationResourceConfig = resourceConfig.getResource(relationName)
-            relationPath.push(relationName)
-
-            if (!joinedTables.some(t => t === relationPath.join('.'))) {
-              let [relation] = localResourceConfig.relationList.filter(r => r.relation === relationName)
-              let table = getTable(localResourceConfig)
-              let localId = `${table}.${relation.localKey}`
-
-              let relationTable = getTable(relationResourceConfig)
-              let foreignId = `${relationTable}.${relationResourceConfig.idAttribute}`
-
-              query = query.join(relationTable, localId, foreignId)
-              joinedTables.push(relationPath.join('.'))
-            }
-            localResourceConfig = relationResourceConfig
-          }
-
-          field = `${getTable(localResourceConfig)}.${parts[0]}`
-        }
-
-        if (op === '==' || op === '===') {
-          query = query.where(field, v)
-        } else if (op === '!=' || op === '!==') {
-          query = query.where(field, '!=', v)
-        } else if (op === '>') {
-          query = query.where(field, '>', v)
-        } else if (op === '>=') {
-          query = query.where(field, '>=', v)
-        } else if (op === '<') {
-          query = query.where(field, '<', v)
-        } else if (op === '<=') {
-          query = query.where(field, '<=', v)
-        // } else if (op === 'isectEmpty') {
-        //  subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)
-        // } else if (op === 'isectNotEmpty') {
-        //  subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)
-        } else if (op === 'in') {
-          query = query.where(field, 'in', v)
-        } else if (op === 'notIn') {
-          query = query.whereNotIn(field, v)
-        } else if (op === 'like') {
-          query = query.where(field, 'like', v)
-        } else if (op === '|==' || op === '|===') {
-          query = query.orWhere(field, v)
-        } else if (op === '|!=' || op === '|!==') {
-          query = query.orWhere(field, '!=', v)
-        } else if (op === '|>') {
-          query = query.orWhere(field, '>', v)
-        } else if (op === '|>=') {
-          query = query.orWhere(field, '>=', v)
-        } else if (op === '|<') {
-          query = query.orWhere(field, '<', v)
-        } else if (op === '|<=') {
-          query = query.orWhere(field, '<=', v)
-        // } else if (op === '|isectEmpty') {
-        //  subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)
-        // } else if (op === '|isectNotEmpty') {
-        //  subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)
-        } else if (op === '|in') {
-          query = query.orWhere(field, 'in', v)
-        } else if (op === '|notIn') {
-          query = query.orWhereNotIn(field, v)
+/**
+ * Lookup and apply table joins to query if field contains a `.`
+ * @param {string} field - Field defined in where filter
+ * @param {object} query - knex query to modify
+ * @param {object} resourceConfig - Resource of primary query/table
+ * @param {string[]} existingJoins - Array of fully qualitifed field names for
+ *   any existing table joins for query
+ * @returns {string} - field updated to perspective of applied joins
+ */
+function applyTableJoins (field, query, resourceConfig, existingJoins) {
+  if (DSUtils.contains(field, '.')) {
+    let parts = field.split('.')
+    let localResourceConfig = resourceConfig
+
+    let relationPath = []
+    while (parts.length >= 2) {
+      let relationName = parts.shift()
+      let relationResourceConfig = resourceConfig.getResource(relationName)
+      relationPath.push(relationName)
+
+      if (!existingJoins.some(t => t === relationPath.join('.'))) {
+        let [relation] = localResourceConfig.relationList.filter(r => r.relation === relationName)
+        if (relation) {
+          let table = getTable(localResourceConfig)
+          let localId = `${table}.${relation.localKey}`
+
+          let relationTable = getTable(relationResourceConfig)
+          let foreignId = `${relationTable}.${relationResourceConfig.idAttribute}`
+
+          query.join(relationTable, localId, foreignId)
+          existingJoins.push(relationPath.join('.'))
         } else {
-          throw new Error('Operator not found')
+          // hopefully a qualified local column
         }
-      })
-    })
-  }
-
-  if (params.orderBy) {
-    if (DSUtils.isString(params.orderBy)) {
-      params.orderBy = [
-        [params.orderBy, 'asc']
-      ]
-    }
-    for (var i = 0; i < params.orderBy.length; i++) {
-      if (DSUtils.isString(params.orderBy[i])) {
-        params.orderBy[i] = [params.orderBy[i], 'asc']
       }
-      query = DSUtils.upperCase(params.orderBy[i][1]) === 'DESC' ? query.orderBy(params.orderBy[i][0], 'desc') : query.orderBy(params.orderBy[i][0], 'asc')
+      localResourceConfig = relationResourceConfig
     }
-  }
-
-  if (params.skip) {
-    query = query.offset(+params.offset)
-  }
 
-  if (params.limit) {
-    query = query.limit(+params.limit)
+    field = `${getTable(localResourceConfig)}.${parts[0]}`
   }
 
-  return query
+  return field;
 }
 
 function loadWithRelations (items, resourceConfig, options) {
@@ -187,7 +96,7 @@ function loadWithRelations (items, resourceConfig, options) {
       if (instance) {
         foreignKeyFilter = { '==': instance[resourceConfig.idAttribute] }
       } else {
-        foreignKeyFilter = { 'in': map(items, item => item[resourceConfig.idAttribute]) }
+        foreignKeyFilter = { 'in': items.map(function (item) { return item[resourceConfig.idAttribute] }) }
       }
       task = this.findAll(resourceConfig.getResource(relationName), {
         where: {
@@ -258,7 +167,7 @@ function loadWithRelations (items, resourceConfig, options) {
         task = this.findAll(resourceConfig.getResource(relationName), {
           where: {
             [relationDef.idAttribute]: {
-              'in': DSUtils.filter(map(items, item => DSUtils.get(item, def.localKey)), x => x)
+              'in': DSUtils.filter(items.map(function (item) { return DSUtils.get(item, def.localKey) }), x => x)
             }
           }
         }, __options).then(relatedItems => {
@@ -297,7 +206,8 @@ class DSSqlAdapter {
     let instance
     options = options || {}
     options.with = options.with || []
-    return this.query
+    let query = options && options.transaction || this.query
+    return query
       .select('*')
       .from(getTable(resourceConfig))
       .where(resourceConfig.idAttribute, toString(id))
@@ -316,7 +226,7 @@ class DSSqlAdapter {
     let items = null
     options = options || {}
     options.with = options.with || []
-    return filterQuery.call(this, resourceConfig, params, options).then(_items => {
+    return this.filterQuery(resourceConfig, params, options).then(_items => {
       items = _items
       return loadWithRelations.call(this, _items, resourceConfig, options)
     }).then(() => items)
@@ -324,7 +234,8 @@ class DSSqlAdapter {
 
   create (resourceConfig, attrs, options) {
     attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || []))
-    return this.query(getTable(resourceConfig))
+    let query = options && options.transaction || this.query
+    return query(getTable(resourceConfig))
       .insert(attrs, resourceConfig.idAttribute)
       .then(ids => {
         if (attrs[resourceConfig.idAttribute]) {
@@ -339,7 +250,8 @@ class DSSqlAdapter {
 
   update (resourceConfig, id, attrs, options) {
     attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || []))
-    return this.query(getTable(resourceConfig))
+    let query = options && options.transaction || this.query
+    return query(getTable(resourceConfig))
       .where(resourceConfig.idAttribute, toString(id))
       .update(attrs)
       .then(() => this.find(resourceConfig, id, options))
@@ -347,27 +259,193 @@ class DSSqlAdapter {
 
   updateAll (resourceConfig, attrs, params, options) {
     attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || []))
-    return filterQuery.call(this, resourceConfig, params, options).then(items => {
-      return map(items, item => item[resourceConfig.idAttribute])
+    return this.filterQuery(resourceConfig, params, options).then(items => {
+      return items.map(function (item) { return item[resourceConfig.idAttribute] })
     }).then(ids => {
-      return filterQuery.call(this, resourceConfig, params, options).update(attrs).then(() => {
+      return this.filterQuery(resourceConfig, params, options).update(attrs).then(() => {
         let _params = {where: {}}
         _params.where[resourceConfig.idAttribute] = {
           'in': ids
         }
-        return filterQuery.call(this, resourceConfig, _params, options)
+        return this.filterQuery(resourceConfig, _params, options)
       })
     })
   }
 
-  destroy (resourceConfig, id) {
-    return this.query(getTable(resourceConfig))
+  destroy (resourceConfig, id, options) {
+    let query = options && options.transaction || this.query
+    return query(getTable(resourceConfig))
       .where(resourceConfig.idAttribute, toString(id))
       .del().then(() => undefined)
   }
 
   destroyAll (resourceConfig, params, options) {
-    return filterQuery.call(this, resourceConfig, params, options).del().then(() => undefined)
+    return this.filterQuery(resourceConfig, params, options).del().then(() => undefined)
+  }
+
+  filterQuery (resourceConfig, params, options) {
+    let table = getTable(resourceConfig)
+    let query
+
+    if (params instanceof Object.getPrototypeOf(this.query.client).QueryBuilder) {
+      query = params
+      params = {}
+    } else if (options && options.query) {
+      query = options.query || this.query
+    } else {
+      query = options && options.transaction || this.query
+      query = query.select(`${table}.*`).from(table)
+    }
+
+    params = params || {}
+    params.where = params.where || {}
+    params.orderBy = params.orderBy || params.sort
+    params.skip = params.skip || params.offset
+
+    let joinedTables = []
+
+    DSUtils.forEach(DSUtils.keys(params), k => {
+      let v = params[k]
+      if (!DSUtils.contains(reserved, k)) {
+        if (DSUtils.isObject(v)) {
+          params.where[k] = v
+        } else {
+          params.where[k] = {
+            '==': v
+          }
+        }
+        delete params[k]
+      }
+    })
+
+    if (!DSUtils.isEmpty(params.where)) {
+      DSUtils.forOwn(params.where, (criteria, field) => {
+        if (!DSUtils.isObject(criteria)) {
+          params.where[field] = {
+            '==': criteria
+          }
+        }
+
+        DSUtils.forOwn(criteria, (v, op) => {
+          // Apply table joins (if needed)
+          if (DSUtils.contains(field, ',')) {
+            let splitFields = field.split(',').map(c => c.trim())
+            field = splitFields.map(splitField => applyTableJoins(splitField, query, resourceConfig, joinedTables)).join(',');
+          } else {
+            field = applyTableJoins(field, query, resourceConfig, joinedTables);
+          }
+
+          if (op === '==' || op === '===') {
+            query = query.where(field, v)
+          } else if (op === '!=' || op === '!==') {
+            query = query.where(field, '!=', v)
+          } else if (op === '>') {
+            query = query.where(field, '>', v)
+          } else if (op === '>=') {
+            query = query.where(field, '>=', v)
+          } else if (op === '<') {
+            query = query.where(field, '<', v)
+          } else if (op === '<=') {
+            query = query.where(field, '<=', v)
+          // } else if (op === 'isectEmpty') {
+          //  subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)
+          // } else if (op === 'isectNotEmpty') {
+          //  subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)
+          } else if (op === 'in') {
+            query = query.where(field, 'in', v)
+          } else if (op === 'notIn') {
+            query = query.whereNotIn(field, v)
+          } else if (op === 'near') {
+            const milesRegex = /(\d+(\.\d+)?)\s*(m|M)iles$/;
+            const kilometersRegex = /(\d+(\.\d+)?)\s*(k|K)$/;
+
+            let radius;
+            let unitsPerDegree;
+            if (typeof v.radius === 'number' || milesRegex.test(v.radius)) {
+              radius = typeof v.radius === 'number' ? v.radius : v.radius.match(milesRegex)[1]
+              unitsPerDegree = 69.0; // miles per degree
+            } else if (kilometersRegex.test(v.radius)) {
+              radius = v.radius.match(kilometersRegex)[1]
+              unitsPerDegree = 111.045; // kilometers per degree;
+            } else {
+              throw new Error('Unknown radius distance units')
+            }
+
+            let [latitudeColumn, longitudeColumn] = field.split(',').map(c => c.trim())
+            let [latitude, longitude] = v.center;
+
+            // Uses indexes on `latitudeColumn` / `longitudeColumn` if available
+            query = query
+              .whereBetween(latitudeColumn, [
+                latitude - (radius / unitsPerDegree),
+                latitude + (radius / unitsPerDegree)
+              ])
+              .whereBetween(longitudeColumn, [
+                longitude - (radius / (unitsPerDegree * Math.cos(latitude * (Math.PI / 180)))),
+                longitude + (radius / (unitsPerDegree * Math.cos(latitude * (Math.PI / 180))))
+              ])
+
+            if (v.calculateDistance) {
+              let distanceColumn = (typeof v.calculateDistance === 'string') ? v.calculateDistance : 'distance'
+              query = query.select(knex.raw(`
+                ${unitsPerDegree} * DEGREES(ACOS(
+                  COS(RADIANS(?)) * COS(RADIANS(${latitudeColumn})) *
+                  COS(RADIANS(${longitudeColumn}) - RADIANS(?)) +
+                  SIN(RADIANS(?)) * SIN(RADIANS(${latitudeColumn}))
+                )) AS ${distanceColumn}`, [latitude, longitude, latitude]))
+            }
+          } else if (op === 'like') {
+            query = query.where(field, 'like', v)
+          } else if (op === '|==' || op === '|===') {
+            query = query.orWhere(field, v)
+          } else if (op === '|!=' || op === '|!==') {
+            query = query.orWhere(field, '!=', v)
+          } else if (op === '|>') {
+            query = query.orWhere(field, '>', v)
+          } else if (op === '|>=') {
+            query = query.orWhere(field, '>=', v)
+          } else if (op === '|<') {
+            query = query.orWhere(field, '<', v)
+          } else if (op === '|<=') {
+            query = query.orWhere(field, '<=', v)
+          // } else if (op === '|isectEmpty') {
+          //  subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)
+          // } else if (op === '|isectNotEmpty') {
+          //  subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)
+          } else if (op === '|in') {
+            query = query.orWhere(field, 'in', v)
+          } else if (op === '|notIn') {
+            query = query.orWhereNotIn(field, v)
+          } else {
+            throw new Error('Operator not found')
+          }
+        })
+      })
+    }
+
+    if (params.orderBy) {
+      if (DSUtils.isString(params.orderBy)) {
+        params.orderBy = [
+          [params.orderBy, 'asc']
+        ]
+      }
+      for (var i = 0; i < params.orderBy.length; i++) {
+        if (DSUtils.isString(params.orderBy[i])) {
+          params.orderBy[i] = [params.orderBy[i], 'asc']
+        }
+        query = DSUtils.upperCase(params.orderBy[i][1]) === 'DESC' ? query.orderBy(params.orderBy[i][0], 'desc') : query.orderBy(params.orderBy[i][0], 'asc')
+      }
+    }
+
+    if (params.skip) {
+      query = query.offset(+params.offset)
+    }
+
+    if (params.limit) {
+      query = query.limit(+params.limit)
+    }
+
+    return query
   }
 }
 
diff --git a/test/create.spec.js b/test/create.spec.js
deleted file mode 100644
index e9fc6b0..0000000
--- a/test/create.spec.js
+++ /dev/null
@@ -1,23 +0,0 @@
-describe('DSSqlAdapter#create', function () {
-  it('should create a user in a sql db', function* () {
-    var createUser = yield adapter.create(User, {name: 'John'});
-    var id = createUser.id;
-    assert.equal(createUser.name, 'John');
-    assert.isDefined(createUser.id);
-
-    var findUser = yield adapter.find(User, createUser.id);
-    assert.equal(findUser.name, 'John');
-    assert.isDefined(findUser.id);
-    assert.equalObjects(findUser, {id: id, name: 'John', age: null, profileId: null});
-
-    var destoryUser = yield adapter.destroy(User, findUser.id);
-    assert.isFalse(!!destoryUser);
-
-    try {
-      var findUser2 = yield adapter.find(User, id);
-      throw new Error('Should not have reached here!');
-    } catch(err) {
-      assert.equal(err.message, 'Not Found!');
-    }
-  });
-});
diff --git a/test/create_trx.spec.js b/test/create_trx.spec.js
new file mode 100644
index 0000000..957dedd
--- /dev/null
+++ b/test/create_trx.spec.js
@@ -0,0 +1,42 @@
+describe('DSSqlAdapter#create + transaction', function () {
+  it('commit should persist created user in a sql db', function* () {
+    var id;
+
+    yield adapter.query.transaction(co.wrap(function * (trx) {
+      var createUser = yield adapter.create(User, {name: 'Jane'}, {transaction: trx});
+      id = createUser.id;
+      assert.equal(createUser.name, 'Jane');
+      assert.isDefined(createUser.id);
+    }));
+
+    var findUser = yield adapter.find(User, id);
+    assert.isObject(findUser, 'user committed to database');
+    assert.equal(findUser.name, 'Jane');
+    assert.isDefined(findUser.id);
+    assert.equalObjects(findUser, {id: id, name: 'Jane', age: null, profileId: null, addressId: null});
+  });
+
+  it('rollback should not persist created user in a sql db', function* () {
+    var id;
+
+    try {
+      yield adapter.query.transaction(co.wrap(function * (trx) {
+        var createUser = yield adapter.create(User, {name: 'John'}, {transaction: trx});
+        id = createUser.id;
+        assert.equal(createUser.name, 'John');
+        assert.isDefined(createUser.id);
+
+        throw new Error('rollback');
+      }));
+    } catch (err) {
+      assert.equal(err.message, 'rollback');
+    }
+
+    try {
+      var findUser = yield adapter.find(User, id);
+      throw new Error('user committed to database');
+    } catch(err) {
+      assert.equal(err.message, 'Not Found!');
+    }
+  });
+});
diff --git a/test/destroy.spec.js b/test/destroy.spec.js
deleted file mode 100644
index 91c75d4..0000000
--- a/test/destroy.spec.js
+++ /dev/null
@@ -1,16 +0,0 @@
-describe('DSSqlAdapter#destroy', function () {
-  it('should destroy a user from a Sql db', function* () {
-    var createUser = yield adapter.create(User, {name: 'John'})
-    var id = createUser.id;
-
-    var destroyUser = yield adapter.destroy(User, createUser.id);
-    assert.isFalse(!!destroyUser);
-
-    try {
-      var findUser = yield adapter.find(User, id);
-      throw new Error('Should not have reached here!');
-    } catch (err) {
-      assert.equal(err.message, 'Not Found!');
-    }
-  });
-});
diff --git a/test/destroyAll.spec.js b/test/destroyAll.spec.js
deleted file mode 100644
index 171dcf1..0000000
--- a/test/destroyAll.spec.js
+++ /dev/null
@@ -1,14 +0,0 @@
-describe('DSSqlAdapter#destroyAll', function () {
-  it('should destroy all items', function* () {
-    var createUser = yield adapter.create(User, {name: 'John'});
-    var id = createUser.id;
-
-    var findUsers = yield adapter.findAll(User, { name: 'John' });
-    assert.equal(findUsers.length, 1);
-    assert.equalObjects(findUsers[0], {id: id, name: 'John', age: null, profileId: null});
-
-    var destroyUser = yield adapter.destroyAll(User, { name: 'John' });
-    var findUsers2 = yield adapter.findAll(User, { name: 'John' });
-    assert.equal(findUsers2.length, 0);
-  });
-});
diff --git a/test/destroy_trx.spec.js b/test/destroy_trx.spec.js
new file mode 100644
index 0000000..2d7b47c
--- /dev/null
+++ b/test/destroy_trx.spec.js
@@ -0,0 +1,35 @@
+describe('DSSqlAdapter#destroy + transaction', function () {
+  it('commit should destroy a user from a Sql db', function* () {
+    var createUser = yield adapter.create(User, {name: 'John'})
+    var id = createUser.id;
+
+    yield adapter.query.transaction(co.wrap(function * (trx) {
+      return adapter.destroy(User, id, {transaction: trx});
+    }));
+
+    try {
+      var findUser = yield adapter.find(User, id);
+      throw new Error('Should not have reached here!');
+    } catch (err) {
+      assert.equal(err.message, 'Not Found!');
+    }
+  });
+
+  it('rollback should not destroy a user from a Sql db', function* () {
+    var createUser = yield adapter.create(User, {name: 'John'})
+    var id = createUser.id;
+
+    try {
+      yield adapter.query.transaction(co.wrap(function * (trx) {
+        yield adapter.destroy(User, createUser.id, {transaction: trx});
+
+        throw new Error('rollback');
+      }));
+    } catch (err) {
+      assert.equal(err.message, 'rollback');
+    }
+
+    var findUser = yield adapter.find(User, id);
+    assert.isObject(findUser, 'user still exists');
+  });
+});
diff --git a/test/filterQuery.spec.js b/test/filterQuery.spec.js
new file mode 100644
index 0000000..2018313
--- /dev/null
+++ b/test/filterQuery.spec.js
@@ -0,0 +1,37 @@
+describe('DSSqlAdapter#filterQuery', function () {
+
+  it('should use built-in query if no custom query provided', function* () {
+    var filterQuery = adapter.filterQuery(User);
+    assert.equal(filterQuery.toString(), 'select `user`.* from `user`')
+  });
+
+  it('should use custom query if passed as params (second parameter)', function* () {
+    var query = adapter.query.from('test');
+    var filterQuery = adapter.filterQuery(User, query);
+    assert.equal(filterQuery.toString(), 'select * from `test`')
+  });
+
+  it('should use custom query if passed as options.query', function* () {
+    var query = adapter.query.from('test');
+    var filterQuery = adapter.filterQuery(User, null, { query });
+    assert.equal(filterQuery.toString(), 'select * from `test`')
+  });
+
+  it('should apply where from params to custom query', function* () {
+    var query = adapter.query.from('test');
+    var filterQuery = adapter.filterQuery(User, { name: 'Sean' }, { query });
+    assert.equal(filterQuery.toString(), 'select * from `test` where `name` = \'Sean\'')
+  });
+
+  it('should apply limit from params to custom query', function* () {
+    var query = adapter.query.from('test');
+    var filterQuery = adapter.filterQuery(User, { limit: 2 }, { query });
+    assert.equal(filterQuery.toString(), 'select * from `test` limit 2')
+  });
+
+  it('should apply order from params to custom query', function* () {
+    var query = adapter.query.from('test');
+    var filterQuery = adapter.filterQuery(User, { orderBy: 'name' }, { query });
+    assert.equal(filterQuery.toString(), 'select * from `test` order by `name` asc')
+  });
+});
diff --git a/test/find.spec.js b/test/find.spec.js
deleted file mode 100644
index 30ca726..0000000
--- a/test/find.spec.js
+++ /dev/null
@@ -1,84 +0,0 @@
-var Promise = require('bluebird');
-describe('DSSqlAdapter#find', function () {
-  it('should find a user in a Sql db', function* () {
-    var user = yield adapter.create(User, {name: 'John'});
-    var userId = user.id;
-    assert.equal(user.name, 'John');
-    assert.isDefined(user.id);
-
-    var user2 = yield adapter.find(User, user.id);
-    assert.equal(user2.name, 'John');
-    assert.isDefined(user2.id);
-    assert.equalObjects(user2, {id: userId, name: 'John', age: null, profileId: null});
-
-    var post = yield adapter.create(Post, { content: 'test', userId: userId });
-    var postId = post.id;
-    assert.equal(post.content, 'test');
-    assert.isDefined(post.id);
-    assert.isDefined(post.userId);
-
-    var comments = yield [
-      adapter.create(Comment, {
-        content: 'test2',
-        postId: post.id,
-        userId: user.id
-      }),
-      adapter.create(Comment, {
-        content: 'test3',
-        postId: post.id,
-        userId: user.id
-      })
-    ];
-
-    comments.sort(function (a, b) {
-      return a.content > b.content;
-    });
-
-    var findPost = yield adapter.find(Post, postId, {with: ['user', 'comment']});
-    findPost.comments.sort(function (a, b) {
-      return a.content > b.content;
-    });
-    assert.equalObjects(findPost.user, user);
-    assert.equalObjects(findPost.comments, comments);
-
-    yield adapter.destroyAll(Comment);
-    yield adapter.destroy(Post, postId);
-    var destroyUser = yield adapter.destroy(User, userId);
-    assert.isFalse(!!destroyUser);
-
-    try {
-      yield adapter.find(User, userId);
-      throw new Error('Should not have reached here!');
-    } catch (err) {
-      assert.equal(err.message, 'Not Found!');
-    }
-  });
-
-  it('should load belongsTo relations', function* () {
-    var profile = yield adapter.create(Profile, { email: 'foo@test.com' });
-    var user = yield adapter.create(User, {name: 'John', profileId: profile.id});
-    var post = yield adapter.create(Post, {content: 'foo', userId: user.id});
-    var comment = yield adapter.create(Comment, { content: 'test2', postId: post.id, userId: post.userId });
-
-    var comment = yield adapter.find(Comment, comment.id, {'with': ['user', 'user.profile', 'post', 'post.user']});
-    assert.isDefined(comment);
-    assert.isDefined(comment.post);
-    assert.isDefined(comment.post.user);
-    assert.isDefined(comment.user);
-    assert.isDefined(comment.user.profile);
-  });
-
-  it('should load hasMany and belongsTo relations', function* () {
-    var profile = yield adapter.create(Profile, { email: 'foo@test.com' });
-    var user = yield adapter.create(User, {name: 'John', profileId: profile.id});
-    var post = yield adapter.create(Post, {content: 'foo', userId: user.id});
-    var comment = yield adapter.create(Comment, { content: 'test2', postId: post.id, userId: post.userId });
-
-    var foundPost = yield adapter.find(Post, post.id, {'with': ['user', 'comment', 'comment.user', 'comment.user.profile']});
-    assert.isDefined(foundPost.comments);
-    assert.isDefined(foundPost.comments[0].user);
-    assert.isDefined(foundPost.comments[0].user.profile);
-    assert.isDefined(foundPost.user);
-  });
-
-});
diff --git a/test/findAll.spec.js b/test/findAll.spec.js
index 7c9448d..7e68253 100644
--- a/test/findAll.spec.js
+++ b/test/findAll.spec.js
@@ -1,185 +1,181 @@
-var Promise = require('bluebird');
-describe('DSSqlAdapter#findAll', function () {
-  it('should filter users', function* () {
-    var users = yield adapter.findAll(User, { age: 30 });
-    assert.equal(users.length, 0);
-
-    var user = yield adapter.create(User, {name: 'John'});
-    var id = user.id;
+'use strict';
 
-    var users2 = yield adapter.findAll(User, { name: 'John' });
-    assert.equal(users2.length, 1);
-    assert.equalObjects(users2[0], {id: id, name: 'John', age: null, profileId: null});
+describe('DSSqlAdapter#findAll', function () {
+  it('should not return relation columns on parent', function* () {
+    let profile1 = yield adapter.create(Profile, { email: 'foo@test.com' });
+    let user1 = yield adapter.create(User, {name: 'John', profileId: profile1.id});
 
-    var destroyedUser = yield adapter.destroy(User, id);
-    assert.isFalse(!!destroyedUser);
+    let users = yield adapter.findAll(User, {'profile.email': 'foo@test.com'});
+    assert.equal(users.length, 1);
+    assert.equal(users[0].profileId, profile1.id);
+    assert.isUndefined(users[0].email);
   });
 
-  it('should filter users using the "in" operator', function* () {
-    var users = yield adapter.findAll(User, {
-      where: {
-        age: {
-          'in': [30]
-        }
-      }
-    });
-    assert.equal(users.length, 0);
+  it('should filter when relations have same column if column is qualified', function* () {
+    let profile1 = yield adapter.create(Profile, { email: 'foo@test.com' });
+    let user1 = yield adapter.create(User, {name: 'John', profileId: profile1.id});
 
-    var user = yield adapter.create(User, {name: 'John'});
-    var id = user.id;
-
-    var users2 = yield adapter.findAll(User, { name: 'John' });
-    assert.equal(users2.length, 1);
-    assert.equalObjects(users2[0], {id: id, name: 'John', age: null, profileId: null});
-
-    var destroyedUser = yield adapter.destroy(User, id);
-    assert.isFalse(!!destroyedUser);
+    // `id` column must be qualified with `user.`
+    let users = yield adapter.findAll(User, {'user.id': user1.id, 'profile.email': 'foo@test.com'});
+    assert.equal(users.length, 1);
+    assert.equal(users[0].profileId, profile1.id);
   });
 
-  it('should filter users using the "like" operator', function* () {
-    var users = yield adapter.findAll(User, {
-      where: {
-        name: {
-          'like': '%J%'
+  describe('near', function () {
+    beforeEach(function * () {
+      this.googleAddress    = yield adapter.create(Address, { name : 'Google',    latitude: 37.4219999, longitude: -122.0862515 });
+      this.appleAddress     = yield adapter.create(Address, { name : 'Apple',     latitude: 37.331852,  longitude: -122.029599 });
+      this.microsoftAddress = yield adapter.create(Address, { name : 'Microsoft', latitude: 47.639649,  longitude: -122.128255 });
+      this.amazonAddress    = yield adapter.create(Address, { name : 'Amazon',    latitude: 47.622915,  longitude: -122.336384 });
+    })
+
+    it('should filter using "near"', function* () {
+      let addresses = yield adapter.findAll(Address, {
+        where: {
+          'latitude,longitude': {
+            'near': {
+              center: [37.41, -122.06],
+              radius: 10
+            }
+          }
         }
-      }
-    });
-    assert.equal(users.length, 0);
-
-    var user = yield adapter.create(User, {name: 'John'});
-    var id = user.id;
-
-    var users2 = yield adapter.findAll(User, {
-      where: {
-        name: {
-          'like': '%J%'
+      });
+      assert.equal(addresses.length, 2);
+      assert.equal(addresses[0].name, 'Google');
+      assert.equal(addresses[1].name, 'Apple');
+    })
+
+    it('should not contain distance column by default', function* () {
+      let addresses = yield adapter.findAll(Address, {
+        where: {
+          'latitude,longitude': {
+            'near': {
+              center: [37.41, -122.06],
+              radius: 5
+            }
+          }
         }
-      }
-    });
-    assert.equal(users2.length, 1);
-    assert.deepEqual(users2[0], {id: id, name: 'John', age: null, profileId: null});
-
-    var destroyedUser = yield adapter.destroy(User, id);
-    assert.isFalse(!!destroyedUser);
-  });
-
-  it('should throw "Operator not found" error', function* () {
-    var op = '>=<';
-
-    assert.throw(function () {
-        return adapter.findAll(User, {
-          where: {
-            name: {
-              op: 'John'
+      });
+      assert.equal(addresses.length, 1);
+      assert.equal(addresses[0].name, 'Google');
+      assert.equal(addresses[0].distance, undefined);
+    })
+
+    it('should contain distance column if "calculateDistance" is truthy', function* () {
+      let addresses = yield adapter.findAll(Address, {
+        where: {
+          'latitude,longitude': {
+            'near': {
+              center: [37.41, -122.06],
+              radius: 10,
+              calculateDistance: true
             }
           }
-        });
-      }
-      , Error, 'Operator not found');
-  });
-
-  it('should load belongsTo relations', function* () {
-    var profile1 = yield adapter.create(Profile, { email: 'foo@test.com' });
-    var user1 = yield adapter.create(User, {name: 'John', profileId: profile1.id});
-    var post1 = yield adapter.create(Post, {content: 'foo', userId: user1.id});
-    var comment1 = yield adapter.create(Comment, { content: 'test2', postId: post1.id, userId: post1.userId });
-
-    var user2 = yield adapter.create(User, {name: 'Sally'});
-    var post2 = yield adapter.create(Post, {content: 'bar', userId: user2.id});
-    var comment2 = yield adapter.create(Comment, { content: 'test3', postId: post2.id, userId: post2.userId });
-
-    var comments = yield adapter.findAll(Comment, {}, {'with': ['user', 'user.profile', 'post', 'post.user']});
-    assert.isDefined(comments[0].post);
-    assert.isDefined(comments[0].post.user);
-    assert.isDefined(comments[0].user);
-    assert.isDefined(comments[0].user.profile || comments[1].user.profile);
-    assert.isDefined(comments[1].post);
-    assert.isDefined(comments[1].post.user);
-    assert.isDefined(comments[1].user);
-  });
-
-  it('should load hasMany and belongsTo relations', function* () {
-    var profile = yield adapter.create(Profile, { email: 'foo@test.com' });
-    var user1 = yield adapter.create(User, {name: 'John', profileId: profile.id});
-    var post1 = yield adapter.create(Post, {content: 'foo', userId: user1.id});
-    var comment1 = yield adapter.create(Comment, { content: 'test2', postId: post1.id, userId: post1.userId });
-
-    var user2 = yield adapter.create(User, {name: 'Sally'});
-    var post2 = yield adapter.create(Post, {content: 'bar', userId: user2.id});
-    var comment2 = yield adapter.create(Comment, { content: 'test3', postId: post2.id, userId: post2.userId });
-
-    var posts = yield adapter.findAll(Post, {}, {'with': ['user', 'comment', 'comment.user', 'comment.user.profile']});
-    assert.isDefined(posts[0].comments);
-    assert.isDefined(posts[0].comments[0].user);
-    assert.isDefined(posts[0].comments[0].user.profile || posts[1].comments[0].user.profile);
-    assert.isDefined(posts[0].user);
-    assert.isDefined(posts[1].comments);
-    assert.isDefined(posts[1].comments[0].user);
-    assert.isDefined(posts[1].user);
-  });
-
-  it('should filter using belongsTo relation', function* () {
-    var profile1 = yield adapter.create(Profile, { email: 'foo@test.com' });
-    var user1 = yield adapter.create(User, {name: 'John', profileId: profile1.id});
-    var post1 = yield adapter.create(Post, {content: 'foo', userId: user1.id});
-    var comment1 = yield adapter.create(Comment, {content: 'test1', postId: post1.id, userId: post1.userId});
-
-    var user2 = yield adapter.create(User, {name: 'Sally'});
-    var post2 = yield adapter.create(Post, {content: 'bar', userId: user2.id});
-    var comment2 = yield adapter.create(Comment, {content: 'test2', postId: post2.id, userId: post2.userId});
-
-    var users = yield adapter.findAll(User, {'profile.email': 'foo@test.com'});
-    assert.equal(users.length, 1);
-    assert.equal(users[0].profileId, profile1.id);
-    assert.equal(users[0].name, 'John');
-  });
-
-  it('should filter through multiple hasOne/belongsTo relations', function* () {
-    var profile1 = yield adapter.create(Profile, { email: 'foo@test.com' });
-    var user1 = yield adapter.create(User, {name: 'John', profileId: profile1.id});
-    var post1 = yield adapter.create(Post, {content: 'foo', userId: user1.id});
-    var comment1 = yield adapter.create(Comment, {content: 'test1', postId: post1.id, userId: post1.userId});
-
-    var profile2 = yield adapter.create(Profile, { email: 'bar@test.com' });
-    var user2 = yield adapter.create(User, {name: 'Sally', profileId: profile2.id});
-    var post2 = yield adapter.create(Post, {content: 'bar', userId: user2.id});
-    var comment2 = yield adapter.create(Comment, {content: 'test2', postId: post2.id, userId: post2.userId});
-
-    var comments = yield adapter.findAll(Comment, { 'user.profile.email': 'foo@test.com' })
-    assert.equal(comments.length, 1);
-    assert.equal(comments[0].userId, user1.id);
-    assert.equal(comments[0].content, 'test1');
-  });
-
-  it('should filter using multiple hasOne/belongsTo relations', function* () {
-    var profile1 = yield adapter.create(Profile, { email: 'foo@test.com' });
-    var user1 = yield adapter.create(User, {name: 'John', profileId: profile1.id});
-    var post1 = yield adapter.create(Post, {content: 'foo', userId: user1.id});
-    var comment1 = yield adapter.create(Comment, {content: 'test1', postId: post1.id, userId: post1.userId});
-
-    var profile2 = yield adapter.create(Profile, { email: 'bar@test.com' });
-    var user2 = yield adapter.create(User, {name: 'Sally', profileId: profile2.id});
-    var post2 = yield adapter.create(Post, {content: 'bar', userId: user2.id});
-    var comment2 = yield adapter.create(Comment, {content: 'test2', postId: post2.id, userId: post2.userId});
-
-    var comments = yield adapter.findAll(Comment, { 'user.name': 'John', 'user.profile.email': 'foo@test.com' })
-    assert.equal(comments.length, 1);
-    assert.equal(comments[0].userId, user1.id);
-    assert.equal(comments[0].content, 'test1');
-  });
-
-  it('should allow passing limit and offset as strings', function* () {
-    var user = yield adapter.findAll(User, {limit: '10', offset: '20'});
-  });
-
-  it('should not return relation columns on parent', function* () {
-    var profile1 = yield adapter.create(Profile, { email: 'foo@test.com' });
-    var user1 = yield adapter.create(User, {name: 'John', profileId: profile1.id});
+        }
+      });
+      assert.equal(addresses.length, 2);
+
+      assert.equal(addresses[0].name, 'Google');
+      assert.isNotNull(addresses[0].distance);
+      assert.equal(Math.round(addresses[0].distance), 2);
+
+      assert.equal(addresses[1].name, 'Apple');
+      assert.isNotNull(addresses[1].distance);
+      assert.equal(Math.round(addresses[1].distance), 6);
+    })
+
+    it('should contain custom distance column if "calculateDistance" is string', function* () {
+      let addresses = yield adapter.findAll(Address, {
+        where: {
+          'latitude,longitude': {
+            'near': {
+              center: [37.41, -122.06],
+              radius: 10,
+              calculateDistance: 'howfar'
+            }
+          }
+        }
+      });
+      assert.equal(addresses.length, 2);
+
+      assert.equal(addresses[0].name, 'Google');
+      assert.equal(addresses[0].distance, undefined);
+      assert.isNotNull(addresses[0].howfar);
+      assert.equal(Math.round(addresses[0].howfar), 2);
+
+      assert.equal(addresses[1].name, 'Apple');
+      assert.equal(addresses[1].distance, undefined);
+      assert.isNotNull(addresses[1].howfar);
+      assert.equal(Math.round(addresses[1].howfar), 6);
+    })
+
+    it('should use kilometers instead of miles if radius ends with "k"', function* () {
+      let addresses = yield adapter.findAll(Address, {
+        where: {
+          'latitude,longitude': {
+            'near': {
+              center: [37.41, -122.06],
+              radius: '10k',
+              calculateDistance: true
+            }
+          }
+        }
+      });
+      assert.equal(addresses.length, 2);
+
+      assert.equal(addresses[0].name, 'Google');
+      assert.isNotNull(addresses[0].distance);
+      assert.equal(Math.round(addresses[0].distance), 3); // in kilometers
+
+      assert.equal(addresses[1].name, 'Apple');
+      assert.isNotNull(addresses[1].distance);
+      assert.equal(Math.round(addresses[1].distance), 9); // in kilometers
+    })
+
+    it('should filter through relationships', function* () {
+      let user1 = yield adapter.create(User, { name : 'Larry Page', addressId: this.googleAddress.id });
+      let user2 = yield adapter.create(User, { name : 'Tim Cook', addressId: this.appleAddress.id });
+
+      let users = yield adapter.findAll(User, {
+        where: {
+          'address.latitude, address.longitude': {
+            'near': {
+              center: [37.41, -122.06],
+              radius: 10,
+              calculateDistance: 'howfar'
+            }
+          }
+        }
+      });
+      assert.equal(users.length, 2);
+      assert.equal(users[0].name, 'Larry Page');
+      assert.equal(users[1].name, 'Tim Cook');
+    })
+
+    it('should filter through multiple hasOne/belongsTo relations', function * () {
+      let user1 = yield adapter.create(User, { name : 'Larry Page', addressId: this.googleAddress.id });
+      var post1 = yield adapter.create(Post, {content: 'foo', userId: user1.id})
+      yield adapter.create(Comment, {content: 'test1', postId: post1.id, userId: post1.userId})
+
+      var user2 = yield adapter.create(User, {name: 'Tim Cook', addressId: this.appleAddress.id})
+      var post2 = yield adapter.create(Post, {content: 'bar', userId: user2.id})
+      yield adapter.create(Comment, {content: 'test2', postId: post2.id, userId: post2.userId})
+
+      let comments = yield adapter.findAll(Comment, {
+        where: {
+          'user.address.latitude, user.address.longitude': {
+            'near': {
+              center: [37.41, -122.06],
+              radius: 5
+            }
+          }
+        }
+      });
 
-    var users = yield adapter.findAll(User, {'profile.email': 'foo@test.com'});
-    assert.equal(users.length, 1);
-    assert.equal(users[0].profileId, profile1.id);
-    assert.isUndefined(users[0].email);
-  });
+      assert.equal(comments.length, 1)
+      assert.equal(comments[0].userId, user1.id)
+      assert.equal(comments[0].content, 'test1')
+    })
 
+  })
 });
diff --git a/test/setup.sql b/test/setup.sql
index cbf6ef3..51b6003 100644
--- a/test/setup.sql
+++ b/test/setup.sql
@@ -1,17 +1,34 @@
+DROP TABLE IF EXISTS `comment`;
+DROP TABLE IF EXISTS `post`;
+DROP TABLE IF EXISTS `user`;
+DROP TABLE IF EXISTS `address`;
+DROP TABLE IF EXISTS `profile`;
+
 CREATE TABLE `profile` (
   `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
   `email` varchar(255) NOT NULL DEFAULT '',
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=45 DEFAULT CHARSET=latin1;
 
+CREATE TABLE `address` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) NOT NULL DEFAULT '',
+  `latitude` Decimal(10,7) DEFAULT NULL,
+  `longitude` Decimal(10,7) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=45 DEFAULT CHARSET=latin1;
+
 CREATE TABLE `user` (
   `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
   `name` varchar(255) NOT NULL DEFAULT '',
   `age` int(11) unsigned DEFAULT NULL,
   `profileId` int(11) unsigned DEFAULT NULL,
+  `addressId` int(11) unsigned DEFAULT NULL,
   PRIMARY KEY (`id`),
   KEY `fk-user-profile` (`profileId`),
-  CONSTRAINT `fk-user-profile` FOREIGN KEY (`profileId`) REFERENCES `profile` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
+  KEY `fk-user-address` (`addressId`),
+  CONSTRAINT `fk-user-profile` FOREIGN KEY (`profileId`) REFERENCES `profile` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
+  CONSTRAINT `fk-user-address` FOREIGN KEY (`addressId`) REFERENCES `address` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
 ) ENGINE=InnoDB AUTO_INCREMENT=45 DEFAULT CHARSET=latin1;
 
 CREATE TABLE `post` (
diff --git a/test/update.spec.js b/test/update.spec.js
deleted file mode 100644
index 03e185d..0000000
--- a/test/update.spec.js
+++ /dev/null
@@ -1,33 +0,0 @@
-describe('DSSqlAdapter#update', function () {
-  it('should update a user in a Sql db', function* () {
-    var user = yield adapter.create(User, {name: 'John'})
-    var id = user.id;
-    assert.equal(user.name, 'John');
-    assert.isDefined(user.id);
-
-    var foundUser = yield adapter.find(User, user.id);
-    assert.equal(foundUser.name, 'John');
-    assert.isDefined(foundUser.id);
-    assert.equalObjects(foundUser, {id: id, name: 'John', age: null, profileId: null});
-
-    var updatedUser = yield adapter.update(User, foundUser.id, {name: 'Johnny'});
-    assert.equal(updatedUser.name, 'Johnny');
-    assert.isDefined(updatedUser.id);
-    assert.equalObjects(updatedUser, {id: id, name: 'Johnny', age: null, profileId: null});
-
-    var foundUser2 = yield adapter.find(User, updatedUser.id);
-    assert.equal(foundUser2.name, 'Johnny');
-    assert.isDefined(foundUser2.id);
-    assert.equalObjects(foundUser2, {id: id, name: 'Johnny', age: null, profileId: null});
-
-    var destroyUser = yield adapter.destroy(User, foundUser2.id);
-    assert.isFalse(!!destroyUser);
-
-    try {
-      yield adapter.find(User, id);
-      throw new Error('Should not have reached here!');
-    } catch (err) {
-      assert.equal(err.message, 'Not Found!');
-    }
-  });
-});
diff --git a/test/updateAll.spec.js b/test/updateAll.spec.js
deleted file mode 100644
index cda2ad1..0000000
--- a/test/updateAll.spec.js
+++ /dev/null
@@ -1,43 +0,0 @@
-describe('DSSqlAdapter#updateAll', function () {
-  it('should update all items', function* () {
-    var user1 = yield adapter.create(User, {name: 'John', age: 20})
-    var userId1 = user1.id;
-
-    var user2 = yield adapter.create(User, {name: 'John', age: 30});
-    var userId2 = user2.id;
-
-    var users = yield adapter.findAll(User, { name: 'John' });
-    users.sort(function (a, b) {
-      return a.age - b.age;
-    });
-    assert.equalObjects(users, [
-      {id: userId1, name: 'John', age: 20, profileId: null},
-      {id: userId2, name: 'John', age: 30, profileId: null}
-    ]);
-
-    var users2 = yield adapter.updateAll(User, { name: 'Johnny' }, { name: 'John' });
-    users2.sort(function (a, b) {
-      return a.age - b.age;
-    });
-    assert.equalObjects(users2, [
-      {id: userId1, name: 'Johnny', age: 20, profileId: null},
-      {id: userId2, name: 'Johnny', age: 30, profileId: null}
-    ]);
-
-    var users3 = yield adapter.findAll(User, { name: 'John' });
-    assert.equalObjects(users3, []);
-    assert.equal(users3.length, 0);
-
-    var users4 = yield adapter.findAll(User, { name: 'Johnny' });
-    users4.sort(function (a, b) {
-      return a.age - b.age;
-    });
-    assert.equalObjects(users4, [
-      {id: userId1, name: 'Johnny', age: 20, profileId: null},
-      {id: userId2, name: 'Johnny', age: 30, profileId: null}
-    ]);
-
-    var destroyedUser = yield adapter.destroyAll(User);
-    assert.isFalse(!!destroyedUser);
-  });
-});
diff --git a/test/update_trx.spec.js b/test/update_trx.spec.js
new file mode 100644
index 0000000..db25567
--- /dev/null
+++ b/test/update_trx.spec.js
@@ -0,0 +1,45 @@
+describe('DSSqlAdapter#update + transaction', function () {
+  it('commit should update a user in a Sql db', function* () {
+    var user = yield adapter.create(User, {name: 'John'})
+    var id = user.id;
+    assert.equal(user.name, 'John');
+    assert.isDefined(user.id);
+
+    yield adapter.query.transaction(co.wrap(function * (trx) {
+      var updatedUser = yield adapter.update(User, id, {name: 'Johnny'}, {transaction: trx});
+      assert.equal(updatedUser.name, 'Johnny');
+      assert.isDefined(updatedUser.id);
+      assert.equalObjects(updatedUser, {id: id, name: 'Johnny', age: null, profileId: null, addressId: null});
+    }));
+
+    var foundUser = yield adapter.find(User, id);
+    assert.equal(foundUser.name, 'Johnny');
+    assert.isDefined(foundUser.id);
+    assert.equalObjects(foundUser, {id: id, name: 'Johnny', age: null, profileId: null, addressId: null});
+  });
+
+  it('rollback should not update a user in a Sql db', function* () {
+    var user = yield adapter.create(User, {name: 'John'})
+    var id = user.id;
+    assert.equal(user.name, 'John');
+    assert.isDefined(user.id);
+
+    try {
+      yield adapter.query.transaction(co.wrap(function * (trx) {
+        var updatedUser = yield adapter.update(User, id, {name: 'Johnny'}, {transaction: trx});
+        assert.equal(updatedUser.name, 'Johnny');
+        assert.isDefined(updatedUser.id);
+        assert.equalObjects(updatedUser, {id: id, name: 'Johnny', age: null, profileId: null, addressId: null});
+
+        throw new Error('rollback');
+      }));
+    } catch (err) {
+      assert.equal(err.message, 'rollback');
+    }
+
+    var foundUser = yield adapter.find(User, id);
+    assert.equal(foundUser.name, 'John');
+    assert.isDefined(foundUser.id);
+    assert.equalObjects(foundUser, {id: id, name: 'John', age: null, profileId: null, addressId: null});
+  });
+});
diff --git a/webpack.config.js b/webpack.config.js
index 0391ee0..7c89b6d 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,5 +1,4 @@
 module.exports = {
-  debug: true,
   entry: './src/index.js',
   output: {
     filename: './dist/js-data-sql.js',
@@ -7,18 +6,22 @@ module.exports = {
     library: 'js-data-sql'
   },
   externals: [
-    'mout/array/map',
-    'mout/lang/toString',
     'mout/string/underscore',
+    'mout/lang/toString',
     'mout/array/unique',
     'js-data',
     'knex'
   ],
   module: {
-    loaders: [{
-      test: /(src)(.+)\.js$/,
-      exclude: /node_modules/,
-      loader: 'babel-loader?blacklist=useStrict'
-    }]
+    loaders: [
+      {
+        test: /(src)(.+)\.js$/,
+        exclude: /node_modules/,
+        loader: 'babel',
+        query: {
+          presets: ['es2015']
+        }
+      }
+    ]
   }
 };